Skip to content

Commit 539dd4f

Browse files
lavarourazvanphp
andauthored
feat(agent): add support for Yii v2 (#848)
Implement Yii v2 instrumentation for auto transaction naming. Original work by @razvanphp in #823. Fixes #821. --------- Co-authored-by: Razvan Grigore <[email protected]>
1 parent eecccd5 commit 539dd4f

File tree

8 files changed

+227
-17
lines changed

8 files changed

+227
-17
lines changed

agent/fw_hooks.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ extern void nr_symfony4_enable(TSRMLS_D);
3939
extern void nr_silex_enable(TSRMLS_D);
4040
extern void nr_slim_enable(TSRMLS_D);
4141
extern void nr_wordpress_enable(TSRMLS_D);
42-
extern void nr_yii_enable(TSRMLS_D);
42+
extern void nr_yii1_enable(TSRMLS_D);
43+
extern void nr_yii2_enable(TSRMLS_D);
4344
extern void nr_zend_enable(TSRMLS_D);
4445
extern void nr_fw_zend2_enable(TSRMLS_D);
4546

agent/fw_yii.c

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "php_agent.h"
66
#include "php_call.h"
77
#include "php_user_instrument.h"
8+
#include "php_error.h"
89
#include "php_execute.h"
910
#include "php_wrapper.h"
1011
#include "fw_hooks.h"
@@ -14,7 +15,7 @@
1415
#include "util_strings.h"
1516

1617
/*
17-
* Set the web transaction name from the action.
18+
* Yii1: Set the web transaction name from the controllerId + actionId combo.
1819
*
1920
* * txn naming scheme:
2021
* In this case, `nr_txn_set_path` is called before `NR_PHP_WRAPPER_CALL` with
@@ -23,8 +24,7 @@
2324
* ensure OAPI compatibility. This entails that the first wrapped call gets to
2425
* name the txn.
2526
*/
26-
27-
NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
27+
NR_PHP_WRAPPER(nr_yii1_runWithParams_wrapper) {
2828
zval* classz = NULL;
2929
zval* idz = NULL;
3030
zval* this_var = NULL;
@@ -37,7 +37,7 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
3737
(void)wraprec;
3838
NR_UNUSED_SPECIALFN;
3939

40-
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII);
40+
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII1);
4141

4242
this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
4343
if (NULL == this_var) {
@@ -76,8 +76,9 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
7676
NR_NOT_OK_TO_OVERWRITE);
7777
}
7878
}
79+
nr_php_zval_free(&idz);
7980
}
80-
81+
nr_php_zval_free(&classz);
8182
end:
8283
NR_PHP_WRAPPER_CALL;
8384

@@ -86,21 +87,138 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
8687
NR_PHP_WRAPPER_END
8788

8889
/*
89-
* Enable Yii instrumentation.
90+
* Enable Yii1 instrumentation.
9091
*/
91-
void nr_yii_enable(TSRMLS_D) {
92+
void nr_yii1_enable(TSRMLS_D) {
9293
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
9394
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
9495
nr_php_wrap_user_function_before_after_clean(
95-
NR_PSTR("CAction::runWithParams"), nr_yii_runWithParams_wrapper, NULL,
96+
NR_PSTR("CAction::runWithParams"), nr_yii1_runWithParams_wrapper, NULL,
9697
NULL);
9798
nr_php_wrap_user_function_before_after_clean(
98-
NR_PSTR("CInlineAction::runWithParams"), nr_yii_runWithParams_wrapper,
99+
NR_PSTR("CInlineAction::runWithParams"), nr_yii1_runWithParams_wrapper,
99100
NULL, NULL);
100101
#else
101102
nr_php_wrap_user_function(NR_PSTR("CAction::runWithParams"),
102-
nr_yii_runWithParams_wrapper TSRMLS_CC);
103+
nr_yii1_runWithParams_wrapper TSRMLS_CC);
103104
nr_php_wrap_user_function(NR_PSTR("CInlineAction::runWithParams"),
104-
nr_yii_runWithParams_wrapper TSRMLS_CC);
105+
nr_yii1_runWithParams_wrapper TSRMLS_CC);
106+
#endif
107+
}
108+
109+
/*
110+
* Yii2: Set the web transaction name from the unique action ID.
111+
*/
112+
NR_PHP_WRAPPER(nr_yii2_runWithParams_wrapper) {
113+
zval* this_var = NULL;
114+
zval* unique_idz = NULL;
115+
const char* unique_id = NULL;
116+
nr_string_len_t unique_id_length;
117+
char* transaction_name = NULL;
118+
119+
(void)wraprec;
120+
NR_UNUSED_SPECIALFN;
121+
122+
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII2);
123+
124+
this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
125+
if (NULL == this_var) {
126+
nrl_verbosedebug(NRL_FRAMEWORK, "Yii2: improper this");
127+
goto end;
128+
}
129+
130+
unique_idz = nr_php_call(this_var, "getUniqueId");
131+
if (nr_php_is_zval_non_empty_string(unique_idz)) {
132+
unique_id = Z_STRVAL_P(unique_idz);
133+
unique_id_length = Z_STRLEN_P(unique_idz);
134+
135+
if (unique_id_length > 256) {
136+
nrl_warning(NRL_FRAMEWORK,
137+
"Yii2 unique ID is too long (> %d); Yii2 naming not used",
138+
256);
139+
} else {
140+
transaction_name = (char*)nr_alloca(unique_id_length + 1);
141+
nr_strxcpy(transaction_name, unique_id, unique_id_length);
142+
143+
nr_txn_set_path("Yii2", NRPRG(txn), transaction_name, NR_PATH_TYPE_ACTION,
144+
NR_NOT_OK_TO_OVERWRITE);
145+
}
146+
} else {
147+
nrl_verbosedebug(NRL_FRAMEWORK,
148+
"getUniqueId does not return a non empty string (%d)",
149+
Z_TYPE_P(unique_idz));
150+
}
151+
nr_php_zval_free(&unique_idz);
152+
end:
153+
NR_PHP_WRAPPER_CALL;
154+
155+
nr_php_scope_release(&this_var);
156+
}
157+
NR_PHP_WRAPPER_END
158+
159+
#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO \
160+
|| defined OVERWRITE_ZEND_EXECUTE_DATA
161+
/*
162+
* Yii2: Report errors and exceptions when built-in ErrorHandler is enabled.
163+
*/
164+
NR_PHP_WRAPPER(nr_yii2_error_handler_wrapper) {
165+
zval* exception = NULL;
166+
167+
NR_UNUSED_SPECIALFN;
168+
(void)wraprec;
169+
170+
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII2);
171+
172+
exception = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
173+
if (!nr_php_is_zval_valid_object(exception)) {
174+
nrl_verbosedebug(NRL_FRAMEWORK, "%s: exception is NULL or not an object",
175+
__func__);
176+
goto end;
177+
}
178+
179+
if (NR_SUCCESS
180+
!= nr_php_error_record_exception(
181+
NRPRG(txn), exception, nr_php_error_get_priority(E_ERROR), true,
182+
"Uncaught exception ", &NRPRG(exception_filters) TSRMLS_CC)) {
183+
nrl_verbosedebug(NRL_FRAMEWORK, "%s: unable to record exception", __func__);
184+
}
185+
186+
end:
187+
nr_php_arg_release(&exception);
188+
}
189+
NR_PHP_WRAPPER_END
190+
#endif
191+
192+
/*
193+
* Enable Yii2 instrumentation.
194+
*/
195+
void nr_yii2_enable(TSRMLS_D) {
196+
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
197+
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
198+
nr_php_wrap_user_function_before_after_clean(
199+
NR_PSTR("yii\\base\\Action::runWithParams"),
200+
nr_yii2_runWithParams_wrapper, NULL, NULL);
201+
nr_php_wrap_user_function_before_after_clean(
202+
NR_PSTR("yii\\base\\InlineAction::runWithParams"),
203+
nr_yii2_runWithParams_wrapper, NULL, NULL);
204+
#else
205+
nr_php_wrap_user_function(NR_PSTR("yii\\base\\Action::runWithParams"),
206+
nr_yii2_runWithParams_wrapper TSRMLS_CC);
207+
nr_php_wrap_user_function(NR_PSTR("yii\\base\\InlineAction::runWithParams"),
208+
nr_yii2_runWithParams_wrapper TSRMLS_CC);
209+
/*
210+
* Wrap Yii2 global error and exception handling methods.
211+
* Given that: ErrorHandler::handleException(), ::handleError() and
212+
* ::handleFatalError() all call ::logException($exception) at the right time,
213+
* we will wrap this one to cover all cases.
214+
* @see
215+
* https://github.com/yiisoft/yii2/blob/master/framework/base/ErrorHandler.php
216+
*
217+
* Note: one can also set YII_ENABLE_ERROR_HANDLER constant to FALSE, this way
218+
* allowing default PHP error handler to be intercepted by the NewRelic agent
219+
* implementation.
220+
*/
221+
nr_php_wrap_user_function(NR_PSTR("yii\\base\\ErrorHandler::logException"),
222+
nr_yii2_error_handler_wrapper TSRMLS_CC);
105223
#endif
106224
}

agent/php_execute.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,9 @@ static const nr_framework_table_t all_frameworks[] = {
416416
{"WordPress", "wordpress", NR_PSTR("wp-config.php"), 0, nr_wordpress_enable,
417417
NR_FW_WORDPRESS},
418418

419-
{"Yii", "yii", NR_PSTR("framework/yii.php"), 0, nr_yii_enable, NR_FW_YII},
420-
{"Yii", "yii", NR_PSTR("framework/yiilite.php"), 0, nr_yii_enable, NR_FW_YII},
419+
{"Yii", "yii", NR_PSTR("framework/yii.php"), 0, nr_yii1_enable, NR_FW_YII1},
420+
{"Yii", "yii", NR_PSTR("framework/yiilite.php"), 0, nr_yii1_enable, NR_FW_YII1},
421+
{"Yii2", "yii2", NR_PSTR("yii2/baseyii.php"), 0, nr_yii2_enable, NR_FW_YII2},
421422

422423
/* See above: Laminas, the successor to Zend, which shares much
423424
of the instrumentation implementation with Zend */
@@ -535,7 +536,6 @@ static nr_library_table_t libraries[] = {
535536
{"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), NULL},
536537
{"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), NULL},
537538
{"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), NULL},
538-
{"Yii2", NR_PSTR("yii2/baseyii.php"), NULL},
539539

540540
/*
541541
* Other CMS (content management systems), detected only, but

agent/php_newrelic.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ typedef enum {
167167
NR_FW_SYMFONY2,
168168
NR_FW_SYMFONY4,
169169
NR_FW_WORDPRESS,
170-
NR_FW_YII,
170+
NR_FW_YII1,
171+
NR_FW_YII2,
171172
NR_FW_ZEND,
172173
NR_FW_ZEND2,
173174
NR_FW_LAMINAS3,

agent/scripts/newrelic.ini.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
639639
; Must be one of the following values:
640640
; cakephp, codeigniter, drupal, drupal8, joomla, kohana, laravel,
641641
; magento, magento2, mediawiki, slim, symfony2, symfony4,
642-
; wordpress, yii, zend, zend2, no_framework
642+
; wordpress, yii, yii2, zend, zend2, no_framework
643643
;
644644
; Note that "drupal" covers only Drupal 6 and 7 and "symfony2"
645645
; now only supports Symfony 3.x.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*DESCRIPTION
8+
The agent should name Yii2 cli transactions
9+
*/
10+
11+
/*EXPECT_METRICS_EXIST
12+
Supportability/framework/Yii2/detected
13+
OtherTransaction/Action/test-integration-cli-action
14+
*/
15+
16+
/*EXPECT_ERROR_EVENTS null */
17+
18+
/* Yii2 mock */
19+
require_once __DIR__.'/yii2/baseyii.php';
20+
21+
$a = new yii\base\InlineAction('test-integration-cli-action');
22+
$a->runWithParams([]);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*DESCRIPTION
8+
The agent should name Yii2 web transactions
9+
*/
10+
11+
/*EXPECT_METRICS_EXIST
12+
Supportability/framework/Yii2/detected
13+
OtherTransaction/Action/test-integration-web-action
14+
*/
15+
16+
/*EXPECT_ERROR_EVENTS null */
17+
18+
/* Yii2 mock */
19+
require_once __DIR__.'/yii2/baseyii.php';
20+
21+
$a = new yii\base\Action('test-integration-web-action');
22+
$a->runWithParams([]);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2020 New Relic Corporation. All rights reserved.
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
/* Mock enough bits of Yii2 infrastructure for naming tests. */
9+
10+
namespace yii\base {
11+
12+
/* Web action */
13+
class Action {
14+
private $uniqid;
15+
16+
public function __construct($argument) {
17+
$this->uniqid = $argument;
18+
}
19+
20+
public function getUniqueId ( ) {
21+
return $this->uniqid;
22+
}
23+
24+
public function runWithParams($params) {
25+
return;
26+
}
27+
}
28+
29+
/* Console action */
30+
class InlineAction {
31+
private $uniqid;
32+
33+
public function __construct($id) {
34+
$this->uniqid = $id;
35+
}
36+
37+
public function getUniqueId ( ) {
38+
return $this->uniqid;
39+
}
40+
41+
public function runWithParams($params) {
42+
return;
43+
}
44+
}
45+
echo "";
46+
}

0 commit comments

Comments
 (0)