Skip to content

Commit bae6375

Browse files
hahuja2lavarou
andauthored
feat(agent): add support for CakePHP 4.x and 5.x (#983)
This PR does the following: - Modifies how the action name is retrieved, so that it is compatible with CakePHP 4.x and 5.x - Adds correct detection file for CakePHP 4.x and 5.x - Adds call to `nr_txn_suggest_package_supportability_metric` in `nr_cakephp_enable` --------- Co-authored-by: Michal Nowacki <[email protected]>
1 parent 467f79e commit bae6375

File tree

3 files changed

+65
-238
lines changed

3 files changed

+65
-238
lines changed

agent/fw_cakephp.c

Lines changed: 62 additions & 220 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
#include "php_agent.h"
7+
#include "php_error.h"
78
#include "php_execute.h"
89
#include "php_user_instrument.h"
910
#include "php_wrapper.h"
@@ -12,132 +13,14 @@
1213
#include "util_logging.h"
1314
#include "util_memory.h"
1415

15-
nr_framework_classification_t nr_cakephp_special_1(
16-
const char* filename TSRMLS_DC) {
17-
NR_UNUSED_TSRMLS;
18-
19-
if (nr_strcaseidx(filename, "cake/libs/object.php") >= 0) {
20-
return FRAMEWORK_IS_SPECIAL;
21-
}
22-
23-
return FRAMEWORK_IS_NORMAL;
24-
}
25-
26-
nr_framework_classification_t nr_cakephp_special_2(
27-
const char* filename TSRMLS_DC) {
28-
NR_UNUSED_TSRMLS;
29-
30-
if (nr_strcaseidx(filename, "cake/core/app.php") >= 0) {
31-
return FRAMEWORK_IS_SPECIAL;
32-
}
33-
34-
return FRAMEWORK_IS_NORMAL;
35-
}
36-
37-
/*
38-
* For CakePHP 1.2 and 1.3 (and possibly earlier versions too) we hook into
39-
* Component::initialize(). This function takes a controller as a parameter
40-
* and we look into the params array of that controller object, and pick up
41-
* the controller and action out of that array.
42-
*
43-
* CakePHP 1.x is end-of-life and no longer supported by the agent.
44-
* Cake PHP 1.x does not support PHP 8+ and this wrapper is not updated for OAPI
45-
* compatibility.
46-
*
47-
*/
48-
NR_PHP_WRAPPER(nr_cakephp_name_the_wt_pre20) {
49-
zval* arg1 = 0;
50-
zval* params;
51-
zval* czval;
52-
zval* azval;
53-
char* controller = 0;
54-
char* action = 0;
55-
int clen = 0;
56-
int alen = 0;
57-
char* name;
58-
59-
(void)wraprec;
60-
NR_UNUSED_SPECIALFN;
61-
62-
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_CAKEPHP);
63-
64-
arg1 = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
65-
if (!nr_php_is_zval_valid_object(arg1)) {
66-
NR_PHP_WRAPPER_CALL;
67-
goto end;
68-
}
69-
70-
NR_PHP_WRAPPER_CALL;
71-
72-
params = nr_php_get_zval_object_property(arg1, "params" TSRMLS_CC);
73-
if (0 == params) {
74-
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: no params found in component");
75-
goto end;
76-
}
77-
78-
if (IS_ARRAY != Z_TYPE_P(params)) {
79-
nrl_verbosedebug(NRL_FRAMEWORK,
80-
"CakePHP: component params is not an array");
81-
goto end;
82-
}
83-
84-
czval = nr_php_get_zval_object_property(params, "controller" TSRMLS_CC);
85-
if (0 == czval) {
86-
nrl_verbosedebug(NRL_FRAMEWORK,
87-
"CakePHP: no params['controller'] in component");
88-
} else {
89-
clen = Z_STRLEN_P(czval);
90-
controller = (char*)nr_alloca(clen + 1);
91-
nr_strxcpy(controller, Z_STRVAL_P(czval), clen);
92-
}
93-
94-
azval = nr_php_get_zval_object_property(params, "action" TSRMLS_CC);
95-
if (0 == azval) {
96-
nrl_verbosedebug(NRL_FRAMEWORK,
97-
"CakePHP: no params['action'] in component");
98-
} else {
99-
alen = Z_STRLEN_P(azval);
100-
action = (char*)nr_alloca(alen + 1);
101-
nr_strxcpy(action, Z_STRVAL_P(azval), alen);
102-
}
103-
104-
if ((0 == clen) && (0 == alen)) {
105-
nrl_verbosedebug(NRL_FRAMEWORK,
106-
"CakePHP: nothing to call the transaction (yet?)");
107-
goto end;
108-
}
109-
110-
name = (char*)nr_alloca(alen + clen + 2);
111-
if (clen) {
112-
nr_strcpy(name, controller);
113-
}
114-
if (alen) {
115-
if (clen) {
116-
nr_strcat(name, "/");
117-
nr_strcat(name, action);
118-
} else {
119-
nr_strcpy(name, action);
120-
}
121-
}
122-
123-
nr_txn_set_path("CakePHP", NRPRG(txn), name, NR_PATH_TYPE_ACTION,
124-
NR_NOT_OK_TO_OVERWRITE);
125-
126-
end:
127-
nr_php_arg_release(&arg1);
128-
}
129-
NR_PHP_WRAPPER_END
16+
#define PHP_PACKAGE_NAME "cakephp/cakephp"
13017

13118
/*
132-
* For CakePHP 2.0 and on, we do things a little differently as the params
133-
* array doesn't exist in the component any more. Instead we hook the
134-
* Controller's invokeAction method. This gets the request as a parameter
135-
* and we get the action from the params array in that object. The
136-
* controller object ($this) has a name, and that name is used (along
137-
* with the word "Controller" appended which is what the CakePHP code does).
138-
*
139-
* CakePHP 2.x is end-of-life and in maintenance mode (critical bugfixes only).
140-
* As such, functionality added in PHP 7.1+ is not well supported.
19+
* For CakePHP 4.0 and on, we retrieve the current controller object
20+
* and are able to extract the controller name from that. We then
21+
* retrieve the request object from the controller and are able to
22+
* extract the action name from that. We then concatenate the two
23+
* strings to form the transaction name.
14124
*
14225
* txn naming scheme:
14326
* In this case, `nr_txn_set_path` is called after `NR_PHP_WRAPPER_CALL` with
@@ -147,17 +30,17 @@ NR_PHP_WRAPPER_END
14730
* default way of calling the wrapped function in func_end.
14831
*
14932
*/
150-
NR_PHP_WRAPPER(nr_cakephp_name_the_wt_2) {
151-
zval* arg1 = 0;
33+
NR_PHP_WRAPPER(nr_cakephp_name_the_wt_4) {
15234
zval* this_var = 0;
15335
zval* czval = 0;
15436
char* controller = 0;
15537
char* action = 0;
15638
int clen = 0;
15739
int alen = 0;
15840
char* name = 0;
159-
zval* params;
160-
zval* azval;
41+
zval* action_zval = NULL;
42+
zval* request = NULL;
43+
zval action_param;
16144

16245
(void)wraprec;
16346
NR_UNUSED_SPECIALFN;
@@ -193,37 +76,24 @@ NR_PHP_WRAPPER(nr_cakephp_name_the_wt_2) {
19376
}
19477
}
19578

196-
arg1 = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
197-
if (!nr_php_is_zval_valid_object(arg1)) {
198-
NR_PHP_WRAPPER_CALL;
199-
goto end;
200-
}
201-
20279
NR_PHP_WRAPPER_CALL;
20380

204-
params = nr_php_get_zval_object_property(arg1, "params" TSRMLS_CC);
205-
if (0 == params) {
206-
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: no params found in request");
81+
request = nr_php_call(this_var, "getRequest");
82+
if (!nr_php_is_zval_valid_object(request)) {
83+
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: no request found in controller");
20784
goto end;
20885
}
20986

210-
if (IS_ARRAY != Z_TYPE_P(params)) {
211-
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: request params is not an array");
87+
nr_php_zval_str(&action_param, "action");
88+
action_zval = nr_php_call(request, "getParam", &action_param);
89+
zval_dtor(&action_param);
90+
if (!nr_php_is_zval_non_empty_string(action_zval)) {
91+
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: no action param found in request");
21292
goto end;
213-
}
214-
215-
azval = nr_php_get_zval_object_property(params, "action" TSRMLS_CC);
216-
if (0 == azval) {
217-
nrl_verbosedebug(NRL_FRAMEWORK, "CakePHP: no params['action'] in request");
21893
} else {
219-
if (!nr_php_is_zval_valid_string(azval)) {
220-
nrl_verbosedebug(NRL_FRAMEWORK,
221-
"CakePHP: no string-valued params['action'] in request");
222-
} else {
223-
alen = Z_STRLEN_P(azval);
224-
action = (char*)nr_alloca(alen + 1);
225-
nr_strxcpy(action, Z_STRVAL_P(azval), alen);
226-
}
94+
alen = Z_STRLEN_P(action_zval);
95+
action = (char*)nr_alloca(alen + 1);
96+
nr_strxcpy(action, Z_STRVAL_P(action_zval), alen);
22797
}
22898

22999
if ((0 == clen) && (0 == alen)) {
@@ -249,92 +119,64 @@ NR_PHP_WRAPPER(nr_cakephp_name_the_wt_2) {
249119
NR_NOT_OK_TO_OVERWRITE);
250120

251121
end:
252-
nr_php_arg_release(&arg1);
253122
nr_php_scope_release(&this_var);
123+
nr_php_zval_free(&request);
124+
nr_php_zval_free(&action_zval);
254125
}
255126
NR_PHP_WRAPPER_END
256127

257128
/*
258-
* CakePHP 1.2, 1.3
259-
*
260-
* Dispatch::cakeError will be called if there is a problem during dispatch
261-
* (action or controller not found).
262-
*
263-
* CakePHP 1.x is end-of-life and no longer supported by the agent.
264-
* Cake PHP 1.x does not support PHP 8+ and this wrapper is not updated for OAPI
265-
* compatibility.
129+
* CakePHP 4.0+
266130
*
131+
* Report errors and exceptions caught by CakePHP's error handler.
267132
*/
268-
NR_PHP_WRAPPER(nr_cakephp_problem_1) {
269-
const char* name = "Dispatcher::cakeError";
133+
NR_PHP_WRAPPER(nr_cakephp_error_handler_wrapper) {
134+
zval* exception = NULL;
135+
char* request_uri = nr_php_get_server_global("REQUEST_URI");
270136

271-
(void)wraprec;
272137
NR_UNUSED_SPECIALFN;
138+
(void)wraprec;
273139

274140
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_CAKEPHP);
275141

276-
nr_txn_set_path("CakePHP", NRPRG(txn), name, NR_PATH_TYPE_ACTION,
277-
NR_NOT_OK_TO_OVERWRITE);
278-
279-
NR_PHP_WRAPPER_CALL;
280-
}
281-
NR_PHP_WRAPPER_END
282-
283-
/*
284-
* CakePHP 2.0+
285-
*
286-
* If the action or controller is not found during the dispatch process, the
287-
* appropriate Exception will be created and thrown. We wrap the CakeException
288-
* constructor instead of the Exception handler, since CakePHP allows for the
289-
* handler to be completely replaced.
290-
*
291-
* CakePHP 2.x is end-of-life and in maintenance mode (critical bugfixes only).
292-
* As such, functionality added in PHP 7.1+ is not well supported.
293-
*
294-
* txn naming scheme:
295-
* In this case, `nr_txn_set_path` is called before `NR_PHP_WRAPPER_CALL` with
296-
* `NR_NOT_OK_TO_OVERWRITE` and as this corresponds to calling the wrapped
297-
* function in func_begin it needs to be explicitly set as a before_callback to
298-
* ensure OAPI compatibility. This entails that the first wrapped call gets to
299-
* name the txn.
300-
*/
301-
NR_PHP_WRAPPER(nr_cakephp_problem_2) {
302-
const char* name = "Exception";
303-
304-
(void)wraprec;
305-
NR_UNUSED_SPECIALFN;
142+
exception = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS);
143+
if (!nr_php_is_zval_valid_object(exception)) {
144+
nrl_verbosedebug(NRL_FRAMEWORK, "%s: exception is NULL or not an object",
145+
__func__);
146+
goto end;
147+
}
306148

307-
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_CAKEPHP);
149+
if (NR_SUCCESS
150+
!= nr_php_error_record_exception(
151+
NRPRG(txn), exception, nr_php_error_get_priority(E_ERROR), true,
152+
"Uncaught exception ", &NRPRG(exception_filters))) {
153+
nrl_verbosedebug(NRL_FRAMEWORK, "%s: unable to record exception", __func__);
154+
}
308155

309-
nr_txn_set_path("CakePHP", NRPRG(txn), name, NR_PATH_TYPE_ACTION,
310-
NR_NOT_OK_TO_OVERWRITE);
156+
if (NULL != request_uri) {
157+
nr_txn_set_path("CakePHP Exception", NRPRG(txn), request_uri, NR_PATH_TYPE_URI,
158+
NR_OK_TO_OVERWRITE);
159+
} else {
160+
nrl_verbosedebug(NRL_FRAMEWORK, "%s: request uri is NULL", __func__);
161+
}
311162

312-
NR_PHP_WRAPPER_CALL;
163+
end:
164+
nr_php_arg_release(&exception);
165+
nr_free(request_uri);
313166
}
314167
NR_PHP_WRAPPER_END
315168

316169
/*
317-
* Enable CakePHP 1.2, 1.3
318-
*/
319-
void nr_cakephp_enable_1(TSRMLS_D) {
320-
nr_php_wrap_user_function(NR_PSTR("Component::initialize"),
321-
nr_cakephp_name_the_wt_pre20 TSRMLS_CC);
322-
nr_php_wrap_user_function(NR_PSTR("Dispatcher::cakeError"),
323-
nr_cakephp_problem_1 TSRMLS_CC);
324-
}
325-
326-
/*
327-
* Enable CakePHP 2.0+
170+
* Enable CakePHP 4.0+
328171
*/
329-
void nr_cakephp_enable_2(TSRMLS_D) {
330-
nr_php_wrap_user_function(NR_PSTR("Controller::invokeAction"),
331-
nr_cakephp_name_the_wt_2 TSRMLS_CC);
332-
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
333-
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
334-
nr_php_wrap_user_function_before_after_clean(
335-
NR_PSTR("CakeException::__construct"), nr_cakephp_problem_2, NULL, NULL);
336-
#else
337-
nr_php_wrap_user_function(NR_PSTR("CakeException::__construct"),
338-
nr_cakephp_problem_2 TSRMLS_CC);
339-
#endif
172+
void nr_cakephp_enable(TSRMLS_D) {
173+
nr_php_wrap_user_function(
174+
NR_PSTR("Cake\\Controller\\Controller::invokeAction"),
175+
nr_cakephp_name_the_wt_4);
176+
nr_php_wrap_user_function(
177+
NR_PSTR(
178+
"Cake\\Error\\Middleware\\ErrorHandlerMiddleware::handleException"),
179+
nr_cakephp_error_handler_wrapper);
180+
nr_txn_suggest_package_supportability_metric(NRPRG(txn), PHP_PACKAGE_NAME,
181+
PHP_PACKAGE_VERSION_UNKNOWN);
340182
}

agent/fw_hooks.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@
1212
*/
1313
#include "php_execute.h"
1414

15-
extern void nr_cakephp_enable_1(TSRMLS_D);
16-
extern void nr_cakephp_enable_2(TSRMLS_D);
17-
extern nr_framework_classification_t nr_cakephp_special_1(
18-
const char* filename TSRMLS_DC);
19-
extern nr_framework_classification_t nr_cakephp_special_2(
20-
const char* filename TSRMLS_DC);
21-
15+
extern void nr_cakephp_enable(TSRMLS_D);
2216
extern void nr_codeigniter_enable(TSRMLS_D);
2317

2418
extern int nr_drupal_is_framework(nrframework_t fw);

agent/php_execute.c

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -331,15 +331,8 @@ typedef struct _nr_framework_table_t {
331331
*/
332332
// clang-format: off
333333
static const nr_framework_table_t all_frameworks[] = {
334-
/*
335-
* Watch out:
336-
* cake1.2 and cake1.3 use a subdirectory named 'cake' (lower case)
337-
* cake2.0 and on use a subdirectory named 'Cake' (upper case file name)
338-
*/
339-
{"CakePHP", "cakephp", NR_PSTR("cake/libs/object.php"), nr_cakephp_special_1,
340-
nr_cakephp_enable_1, NR_FW_CAKEPHP},
341-
{"CakePHP", "cakephp", NR_PSTR("cake/core/app.php"), nr_cakephp_special_2,
342-
nr_cakephp_enable_2, NR_FW_CAKEPHP},
334+
{"CakePHP", "cakephp", NR_PSTR("cakephp/src/core/functions.php"), 0,
335+
nr_cakephp_enable, NR_FW_CAKEPHP},
343336

344337
/*
345338
* Watch out: frameworks or CMS' build on top of CodeIgniter might not get
@@ -522,8 +515,6 @@ static nr_library_table_t libraries[] = {
522515
* with other frameworks or even without a framework at all.
523516
*/
524517
{"Laminas_Http", NR_PSTR("laminas-http/src/client.php"), nr_laminas_http_enable},
525-
526-
{"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), NULL},
527518
};
528519
// clang-format: on
529520

0 commit comments

Comments
 (0)