Skip to content

Commit bbd3a9c

Browse files
authored
feat: Add CakePHP 3+ Support (#2618)
* feat: Add CakePHP 3 Support * tests: Test for 7.1+ (requires intl) + typo * feat: Retrieve extension, plugin, and theme information * tests: Add CakePHP v4.5 tests * tests: Add CakePHP v5.0 tests * style: Use `hook_method` * fix: Fetch root span from `\DDTrace\root_span()` * style: refactor common logic
1 parent e721e34 commit bbd3a9c

File tree

285 files changed

+12634
-103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

285 files changed

+12634
-103
lines changed

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ TEST_INTEGRATIONS_71 := \
557557
TEST_WEB_71 := \
558558
test_metrics \
559559
test_web_cakephp_28 \
560+
test_web_cakephp_310 \
560561
test_web_codeigniter_22 \
561562
test_web_codeigniter_31 \
562563
test_web_laravel_42 \
@@ -610,6 +611,7 @@ TEST_INTEGRATIONS_72 := \
610611

611612
TEST_WEB_72 := \
612613
test_metrics \
614+
test_web_cakephp_310 \
613615
test_web_codeigniter_22 \
614616
test_web_codeigniter_31 \
615617
test_web_drupal_89 \
@@ -669,6 +671,7 @@ TEST_INTEGRATIONS_73 :=\
669671

670672
TEST_WEB_73 := \
671673
test_metrics \
674+
test_web_cakephp_310 \
672675
test_web_codeigniter_22 \
673676
test_web_codeigniter_31 \
674677
test_web_drupal_89 \
@@ -730,6 +733,8 @@ TEST_INTEGRATIONS_74 := \
730733

731734
TEST_WEB_74 := \
732735
test_metrics \
736+
test_web_cakephp_310 \
737+
test_web_cakephp_45 \
733738
test_web_codeigniter_22 \
734739
test_web_codeigniter_31 \
735740
test_web_drupal_89 \
@@ -792,6 +797,7 @@ TEST_INTEGRATIONS_80 := \
792797

793798
TEST_WEB_80 := \
794799
test_metrics \
800+
test_web_cakephp_45 \
795801
test_web_codeigniter_22 \
796802
test_web_codeigniter_31 \
797803
test_web_drupal_95 \
@@ -840,6 +846,8 @@ TEST_INTEGRATIONS_81 := \
840846

841847
TEST_WEB_81 := \
842848
test_metrics \
849+
test_web_cakephp_45 \
850+
test_web_cakephp_50 \
843851
test_web_codeigniter_22 \
844852
test_web_codeigniter_31 \
845853
test_web_drupal_95 \
@@ -891,6 +899,8 @@ TEST_INTEGRATIONS_82 := \
891899

892900
TEST_WEB_82 := \
893901
test_metrics \
902+
test_web_cakephp_45 \
903+
test_web_cakephp_50 \
894904
test_web_codeigniter_22 \
895905
test_web_codeigniter_31 \
896906
test_web_drupal_95 \
@@ -946,6 +956,8 @@ TEST_INTEGRATIONS_83 := \
946956

947957
TEST_WEB_83 := \
948958
test_metrics \
959+
test_web_cakephp_45 \
960+
test_web_cakephp_50 \
949961
test_web_codeigniter_22 \
950962
test_web_codeigniter_31 \
951963
test_web_drupal_95 \
@@ -1230,6 +1242,15 @@ test_integrations_swoole_5: global_test_run_dependencies
12301242
test_web_cakephp_28: global_test_run_dependencies
12311243
$(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_2_8,)
12321244
$(call run_tests_debug,--testsuite=cakephp-28-test)
1245+
test_web_cakephp_310: global_test_run_dependencies
1246+
$(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_3_10,)
1247+
$(call run_tests_debug,--testsuite=cakephp-310-test)
1248+
test_web_cakephp_45: global_test_run_dependencies
1249+
$(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_4_5,)
1250+
$(call run_tests_debug,--testsuite=cakephp-45-test)
1251+
test_web_cakephp_50: global_test_run_dependencies
1252+
$(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_5_0,)
1253+
$(call run_tests_debug,--testsuite=cakephp-50-test)
12331254
test_web_codeigniter_22: global_test_run_dependencies
12341255
$(call run_tests_debug,--testsuite=codeigniter-22-test)
12351256
test_web_codeigniter_31: global_test_run_dependencies

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"sort-packages": true,
3737
"allow-plugins": {
3838
"g1a/composer-test-scenarios": true,
39-
"php-http/discovery": true
39+
"php-http/discovery": true,
40+
"cakephp/plugin-installer": true
4041
}
4142
},
4243
"autoload": {

ext/integrations/integrations.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ void ddtrace_integrations_minit(void) {
227227
"DDTrace\\Integrations\\CakePHP\\CakePHPIntegration");
228228
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_CAKEPHP, "Dispatcher", "__construct",
229229
"DDTrace\\Integrations\\CakePHP\\CakePHPIntegration");
230+
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_CAKEPHP, "App\\Application", "__construct",
231+
"DDTrace\\Integrations\\CakePHP\\CakePHPIntegration");
232+
DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_CAKEPHP, "Cake\\Http\\Server", "__construct",
233+
"DDTrace\\Integrations\\CakePHP\\CakePHPIntegration");
230234

231235
DD_SET_UP_DEFERRED_LOADING_BY_FUNCTION(DDTRACE_INTEGRATION_EXEC, "exec",
232236
"DDTrace\\Integrations\\Exec\\ExecIntegration");

src/DDTrace/Integrations/CakePHP/CakePHPIntegration.php

Lines changed: 39 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22

33
namespace DDTrace\Integrations\CakePHP;
44

5-
use CakeRequest;
5+
use DDTrace\Integrations\CakePHP\V2\CakePHPIntegrationLoader as CakePHPIntegrationLoaderV2;
6+
use DDTrace\Integrations\CakePHP\V3\CakePHPIntegrationLoader as CakePHPIntegrationLoaderV3;
67
use DDTrace\Integrations\Integration;
7-
use DDTrace\SpanData;
88
use DDTrace\Tag;
9-
use DDTrace\Type;
10-
use DDTrace\Util\Normalizer;
11-
use Router;
129

1310
class CakePHPIntegration extends Integration
1411
{
1512
const NAME = 'cakephp';
1613

1714
public $appName;
1815
public $rootSpan;
16+
public $setRootSpanInfoFn;
17+
public $handleExceptionFn;
18+
public $setStatusCodeFn;
19+
public $parseRouteFn;
1920

2021
/**
2122
* @return string The integration name.
@@ -25,122 +26,59 @@ public function getName()
2526
return self::NAME;
2627
}
2728

28-
// CakePHP v2.x - we don't need to check for v3 since it does not have \Dispatcher or \ShellDispatcher
2929
public function init(): int
3030
{
3131
$integration = $this;
3232

33-
// Since "Dispatcher" and "App" are common names, check for a CakePHP signature before loading
34-
if (!defined('CAKE_CORE_INCLUDE_PATH')) {
35-
return self::NOT_AVAILABLE;
36-
}
37-
38-
$integration->rootSpan = null;
39-
40-
$setRootSpanInfoFn = function () use ($integration) {
33+
$integration->setRootSpanInfoFn = function () use ($integration) {
4134
$rootSpan = \DDTrace\root_span();
4235
if ($rootSpan === null) {
4336
return;
4437
}
4538

4639
$integration->appName = \ddtrace_config_app_name(CakePHPIntegration::NAME);
47-
$integration->rootSpan = $rootSpan;
48-
$integration->addTraceAnalyticsIfEnabled($integration->rootSpan);
49-
$integration->rootSpan->service = $integration->appName;
40+
$integration->addTraceAnalyticsIfEnabled($rootSpan);
41+
$rootSpan->service = $integration->appName;
5042
if ('cli' === PHP_SAPI) {
51-
$integration->rootSpan->name = 'cakephp.console';
52-
$integration->rootSpan->resource =
53-
!empty($_SERVER['argv'][1]) ? 'cake_console ' . $_SERVER['argv'][1] : 'cake_console';
43+
$rootSpan->name = 'cakephp.console';
44+
$rootSpan->resource = !empty($_SERVER['argv'][1])
45+
? 'cake_console ' . $_SERVER['argv'][1]
46+
: 'cake_console';
5447
} else {
55-
$integration->rootSpan->name = 'cakephp.request';
56-
$integration->rootSpan->meta[Tag::SPAN_KIND] = 'server';
48+
$rootSpan->name = 'cakephp.request';
49+
$rootSpan->meta[Tag::SPAN_KIND] = 'server';
5750
}
58-
$integration->rootSpan->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
51+
$rootSpan->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
5952
};
6053

61-
\DDTrace\hook_method('App', 'init', $setRootSpanInfoFn);
62-
\DDTrace\hook_method('Dispatcher', '__construct', $setRootSpanInfoFn);
63-
64-
\DDTrace\trace_method(
65-
'Controller',
66-
'invokeAction',
67-
function (SpanData $span, array $args) use ($integration) {
68-
$span->name = $span->resource = 'Controller.invokeAction';
69-
$span->type = Type::WEB_SERVLET;
70-
$span->service = $integration->appName;
71-
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
72-
73-
$request = $args[0];
74-
if (!$request instanceof CakeRequest) {
75-
return;
76-
}
77-
78-
if (dd_trace_env_config("DD_HTTP_SERVER_ROUTE_BASED_NAMING")) {
79-
$integration->rootSpan->resource =
80-
$_SERVER['REQUEST_METHOD'] . ' ' . $this->name . 'Controller@' . $request->params['action'];
81-
}
82-
83-
if (!array_key_exists(Tag::HTTP_URL, $integration->rootSpan->meta)) {
84-
$integration->rootSpan->meta[Tag::HTTP_URL] = Router::url($request->here, true)
85-
. Normalizer::sanitizedQueryString();
86-
}
87-
$integration->rootSpan->meta['cakephp.route.controller'] = $request->params['controller'];
88-
$integration->rootSpan->meta['cakephp.route.action'] = $request->params['action'];
89-
if (isset($request->params['plugin'])) {
90-
$integration->rootSpan->meta['cakephp.plugin'] = $request->params['plugin'];
91-
}
54+
$integration->handleExceptionFn = function ($This, $scope, $args) use ($integration) {
55+
$rootSpan = \DDTrace\root_span();
56+
if ($rootSpan !== null) {
57+
$integration->setError($rootSpan, $args[0]);
9258
}
93-
);
94-
95-
// This only traces the default exception renderer
96-
// Remove this when error tracking is added
97-
// Other possible places to trace
98-
// - ErrorHandler::handleException()
99-
// - Controller::appError()
100-
// - Exception.handler
101-
// - Exception.renderer
102-
\DDTrace\trace_method('ExceptionRenderer', '__construct', [
103-
'instrument_when_limited' => 1,
104-
'posthook' => function (SpanData $span, array $args) use ($integration) {
105-
$integration->setError($integration->rootSpan, $args[0]);
106-
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
107-
return false;
108-
},
109-
]);
110-
111-
\DDTrace\trace_method('CakeResponse', 'statusCode', [
112-
'instrument_when_limited' => 1,
113-
'posthook' => function (SpanData $span, $args, $return) use ($integration) {
114-
$integration->rootSpan->meta[Tag::HTTP_STATUS_CODE] = $return;
115-
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
116-
return false;
117-
},
118-
]);
59+
};
11960

120-
// Create a trace span for every template rendered
121-
\DDTrace\trace_method('View', 'render', function (SpanData $span) use ($integration) {
122-
$span->name = 'cakephp.view';
123-
$span->type = Type::WEB_SERVLET;
124-
$file = $this->viewPath . '/' . $this->view . $this->ext;
125-
$span->resource = $file;
126-
$span->meta = ['cakephp.view' => $file];
127-
$span->service = $integration->appName;
128-
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
129-
});
61+
$integration->setStatusCodeFn = function ($This, $scope, $args, $retval) use ($integration) {
62+
$rootSpan = \DDTrace\root_span();
63+
if ($rootSpan) {
64+
$rootSpan->meta[Tag::HTTP_STATUS_CODE] = $retval;
65+
}
66+
};
13067

131-
\DDTrace\hook_method(
132-
'CakeRoute',
133-
'parse',
134-
null,
135-
function ($app, $appClass, $args, $retval) use ($integration) {
136-
if (!$retval) {
137-
return;
138-
}
68+
$integration->parseRouteFn = function ($app, $appClass, $args, $retval) use ($integration) {
69+
if (!$retval) {
70+
return;
71+
}
13972

140-
$integration->rootSpan->meta[Tag::HTTP_ROUTE] = $app->template;
73+
$rootSpan = \DDTrace\root_span();
74+
if ($rootSpan !== null) {
75+
$rootSpan->meta[Tag::HTTP_ROUTE] = $app->template;
14176
}
142-
);
77+
};
14378

144-
return Integration::LOADED;
79+
$loader = class_exists('Cake\Http\Server') // Only exists in V3+
80+
? new CakePHPIntegrationLoaderV3()
81+
: new CakePHPIntegrationLoaderV2();
82+
return $loader->load($integration);
14583
}
14684
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace DDTrace\Integrations\CakePHP\V2;
4+
5+
use CakeRequest;
6+
use DDTrace\Integrations\CakePHP\CakePHPIntegration;
7+
use DDTrace\Integrations\Integration;
8+
use DDTrace\SpanData;
9+
use DDTrace\Tag;
10+
use DDTrace\Type;
11+
use DDTrace\Util\Normalizer;
12+
use Router;
13+
14+
class CakePHPIntegrationLoader
15+
{
16+
// CakePHP v2.x - we don't need to check for v3 since it does not have \Dispatcher or \ShellDispatcher
17+
public function load($integration)
18+
{
19+
if (!defined('CAKE_CORE_INCLUDE_PATH')) {
20+
return Integration::NOT_AVAILABLE;
21+
}
22+
23+
\DDTrace\hook_method('App', 'init', $integration->setRootSpanInfoFn);
24+
\DDTrace\hook_method('Dispatcher', '__construct', $integration->setRootSpanInfoFn);
25+
26+
\DDTrace\trace_method(
27+
'Controller',
28+
'invokeAction',
29+
function (SpanData $span, array $args) use ($integration) {
30+
$span->name = $span->resource = 'Controller.invokeAction';
31+
$span->type = Type::WEB_SERVLET;
32+
$span->service = $integration->appName;
33+
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
34+
35+
$request = $args[0];
36+
if (!$request instanceof CakeRequest) {
37+
return;
38+
}
39+
40+
$rootSpan = \DDTrace\root_span();
41+
42+
if (dd_trace_env_config("DD_HTTP_SERVER_ROUTE_BASED_NAMING")) {
43+
$rootSpan->resource =
44+
$_SERVER['REQUEST_METHOD'] . ' ' . $this->name . 'Controller@' . $request->params['action'];
45+
}
46+
47+
if (!array_key_exists(Tag::HTTP_URL, $rootSpan->meta)) {
48+
$rootSpan->meta[Tag::HTTP_URL] = Router::url($request->here, true)
49+
. Normalizer::sanitizedQueryString();
50+
}
51+
$rootSpan->meta['cakephp.route.controller'] = $request->params['controller'];
52+
$rootSpan->meta['cakephp.route.action'] = $request->params['action'];
53+
if (isset($request->params['plugin'])) {
54+
$rootSpan->meta['cakephp.plugin'] = $request->params['plugin'];
55+
}
56+
}
57+
);
58+
59+
// This only traces the default exception renderer
60+
// Remove this when error tracking is added
61+
// Other possible places to trace
62+
// - ErrorHandler::handleException()
63+
// - Controller::appError()
64+
// - Exception.handler
65+
// - Exception.renderer
66+
\DDTrace\hook_method(
67+
'ExceptionRenderer',
68+
'__construct',
69+
$integration->handleExceptionFn
70+
);
71+
72+
\DDTrace\hook_method(
73+
'CakeResponse',
74+
'statusCode',
75+
null,
76+
$integration->setStatusCodeFn
77+
);
78+
79+
// Create a trace span for every template rendered
80+
\DDTrace\trace_method('View', 'render', function (SpanData $span) use ($integration) {
81+
$span->name = 'cakephp.view';
82+
$span->type = Type::WEB_SERVLET;
83+
$file = $this->viewPath . '/' . $this->view . $this->ext;
84+
$span->resource = $file;
85+
$span->meta = ['cakephp.view' => $file];
86+
$span->service = $integration->appName;
87+
$span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME;
88+
});
89+
90+
\DDTrace\hook_method(
91+
'CakeRoute',
92+
'parse',
93+
null,
94+
$integration->parseRouteFn
95+
);
96+
97+
return Integration::LOADED;
98+
}
99+
}

0 commit comments

Comments
 (0)