Skip to content

Commit

Permalink
feat(agent): add support for Yii v2 (#848)
Browse files Browse the repository at this point in the history
Implement Yii v2 instrumentation for auto transaction naming.

Original work by @razvanphp in #823. Fixes #821.

---------

Co-authored-by: Razvan Grigore <[email protected]>
  • Loading branch information
lavarou and razvanphp authored Apr 19, 2024
1 parent eecccd5 commit 539dd4f
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 17 deletions.
3 changes: 2 additions & 1 deletion agent/fw_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ extern void nr_symfony4_enable(TSRMLS_D);
extern void nr_silex_enable(TSRMLS_D);
extern void nr_slim_enable(TSRMLS_D);
extern void nr_wordpress_enable(TSRMLS_D);
extern void nr_yii_enable(TSRMLS_D);
extern void nr_yii1_enable(TSRMLS_D);
extern void nr_yii2_enable(TSRMLS_D);
extern void nr_zend_enable(TSRMLS_D);
extern void nr_fw_zend2_enable(TSRMLS_D);

Expand Down
140 changes: 129 additions & 11 deletions agent/fw_yii.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "php_agent.h"
#include "php_call.h"
#include "php_user_instrument.h"
#include "php_error.h"
#include "php_execute.h"
#include "php_wrapper.h"
#include "fw_hooks.h"
Expand All @@ -14,7 +15,7 @@
#include "util_strings.h"

/*
* Set the web transaction name from the action.
* Yii1: Set the web transaction name from the controllerId + actionId combo.
*
* * txn naming scheme:
* In this case, `nr_txn_set_path` is called before `NR_PHP_WRAPPER_CALL` with
Expand All @@ -23,8 +24,7 @@
* ensure OAPI compatibility. This entails that the first wrapped call gets to
* name the txn.
*/

NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
NR_PHP_WRAPPER(nr_yii1_runWithParams_wrapper) {
zval* classz = NULL;
zval* idz = NULL;
zval* this_var = NULL;
Expand All @@ -37,7 +37,7 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
(void)wraprec;
NR_UNUSED_SPECIALFN;

NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII);
NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII1);

this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
if (NULL == this_var) {
Expand Down Expand Up @@ -76,8 +76,9 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
NR_NOT_OK_TO_OVERWRITE);
}
}
nr_php_zval_free(&idz);
}

nr_php_zval_free(&classz);
end:
NR_PHP_WRAPPER_CALL;

Expand All @@ -86,21 +87,138 @@ NR_PHP_WRAPPER(nr_yii_runWithParams_wrapper) {
NR_PHP_WRAPPER_END

/*
* Enable Yii instrumentation.
* Enable Yii1 instrumentation.
*/
void nr_yii_enable(TSRMLS_D) {
void nr_yii1_enable(TSRMLS_D) {
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
nr_php_wrap_user_function_before_after_clean(
NR_PSTR("CAction::runWithParams"), nr_yii_runWithParams_wrapper, NULL,
NR_PSTR("CAction::runWithParams"), nr_yii1_runWithParams_wrapper, NULL,
NULL);
nr_php_wrap_user_function_before_after_clean(
NR_PSTR("CInlineAction::runWithParams"), nr_yii_runWithParams_wrapper,
NR_PSTR("CInlineAction::runWithParams"), nr_yii1_runWithParams_wrapper,
NULL, NULL);
#else
nr_php_wrap_user_function(NR_PSTR("CAction::runWithParams"),
nr_yii_runWithParams_wrapper TSRMLS_CC);
nr_yii1_runWithParams_wrapper TSRMLS_CC);
nr_php_wrap_user_function(NR_PSTR("CInlineAction::runWithParams"),
nr_yii_runWithParams_wrapper TSRMLS_CC);
nr_yii1_runWithParams_wrapper TSRMLS_CC);
#endif
}

/*
* Yii2: Set the web transaction name from the unique action ID.
*/
NR_PHP_WRAPPER(nr_yii2_runWithParams_wrapper) {
zval* this_var = NULL;
zval* unique_idz = NULL;
const char* unique_id = NULL;
nr_string_len_t unique_id_length;
char* transaction_name = NULL;

(void)wraprec;
NR_UNUSED_SPECIALFN;

NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII2);

this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
if (NULL == this_var) {
nrl_verbosedebug(NRL_FRAMEWORK, "Yii2: improper this");
goto end;
}

unique_idz = nr_php_call(this_var, "getUniqueId");
if (nr_php_is_zval_non_empty_string(unique_idz)) {
unique_id = Z_STRVAL_P(unique_idz);
unique_id_length = Z_STRLEN_P(unique_idz);

if (unique_id_length > 256) {
nrl_warning(NRL_FRAMEWORK,
"Yii2 unique ID is too long (> %d); Yii2 naming not used",
256);
} else {
transaction_name = (char*)nr_alloca(unique_id_length + 1);
nr_strxcpy(transaction_name, unique_id, unique_id_length);

nr_txn_set_path("Yii2", NRPRG(txn), transaction_name, NR_PATH_TYPE_ACTION,
NR_NOT_OK_TO_OVERWRITE);
}
} else {
nrl_verbosedebug(NRL_FRAMEWORK,
"getUniqueId does not return a non empty string (%d)",
Z_TYPE_P(unique_idz));
}
nr_php_zval_free(&unique_idz);
end:
NR_PHP_WRAPPER_CALL;

nr_php_scope_release(&this_var);
}
NR_PHP_WRAPPER_END

#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO \
|| defined OVERWRITE_ZEND_EXECUTE_DATA
/*
* Yii2: Report errors and exceptions when built-in ErrorHandler is enabled.
*/
NR_PHP_WRAPPER(nr_yii2_error_handler_wrapper) {
zval* exception = NULL;

NR_UNUSED_SPECIALFN;
(void)wraprec;

NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_YII2);

exception = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
if (!nr_php_is_zval_valid_object(exception)) {
nrl_verbosedebug(NRL_FRAMEWORK, "%s: exception is NULL or not an object",
__func__);
goto end;
}

if (NR_SUCCESS
!= nr_php_error_record_exception(
NRPRG(txn), exception, nr_php_error_get_priority(E_ERROR), true,
"Uncaught exception ", &NRPRG(exception_filters) TSRMLS_CC)) {
nrl_verbosedebug(NRL_FRAMEWORK, "%s: unable to record exception", __func__);
}

end:
nr_php_arg_release(&exception);
}
NR_PHP_WRAPPER_END
#endif

/*
* Enable Yii2 instrumentation.
*/
void nr_yii2_enable(TSRMLS_D) {
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
nr_php_wrap_user_function_before_after_clean(
NR_PSTR("yii\\base\\Action::runWithParams"),
nr_yii2_runWithParams_wrapper, NULL, NULL);
nr_php_wrap_user_function_before_after_clean(
NR_PSTR("yii\\base\\InlineAction::runWithParams"),
nr_yii2_runWithParams_wrapper, NULL, NULL);
#else
nr_php_wrap_user_function(NR_PSTR("yii\\base\\Action::runWithParams"),
nr_yii2_runWithParams_wrapper TSRMLS_CC);
nr_php_wrap_user_function(NR_PSTR("yii\\base\\InlineAction::runWithParams"),
nr_yii2_runWithParams_wrapper TSRMLS_CC);
/*
* Wrap Yii2 global error and exception handling methods.
* Given that: ErrorHandler::handleException(), ::handleError() and
* ::handleFatalError() all call ::logException($exception) at the right time,
* we will wrap this one to cover all cases.
* @see
* https://github.com/yiisoft/yii2/blob/master/framework/base/ErrorHandler.php
*
* Note: one can also set YII_ENABLE_ERROR_HANDLER constant to FALSE, this way
* allowing default PHP error handler to be intercepted by the NewRelic agent
* implementation.
*/
nr_php_wrap_user_function(NR_PSTR("yii\\base\\ErrorHandler::logException"),
nr_yii2_error_handler_wrapper TSRMLS_CC);
#endif
}
6 changes: 3 additions & 3 deletions agent/php_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,9 @@ static const nr_framework_table_t all_frameworks[] = {
{"WordPress", "wordpress", NR_PSTR("wp-config.php"), 0, nr_wordpress_enable,
NR_FW_WORDPRESS},

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

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

/*
* Other CMS (content management systems), detected only, but
Expand Down
3 changes: 2 additions & 1 deletion agent/php_newrelic.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ typedef enum {
NR_FW_SYMFONY2,
NR_FW_SYMFONY4,
NR_FW_WORDPRESS,
NR_FW_YII,
NR_FW_YII1,
NR_FW_YII2,
NR_FW_ZEND,
NR_FW_ZEND2,
NR_FW_LAMINAS3,
Expand Down
2 changes: 1 addition & 1 deletion agent/scripts/newrelic.ini.template
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
; Must be one of the following values:
; cakephp, codeigniter, drupal, drupal8, joomla, kohana, laravel,
; magento, magento2, mediawiki, slim, symfony2, symfony4,
; wordpress, yii, zend, zend2, no_framework
; wordpress, yii, yii2, zend, zend2, no_framework
;
; Note that "drupal" covers only Drupal 6 and 7 and "symfony2"
; now only supports Symfony 3.x.
Expand Down
22 changes: 22 additions & 0 deletions tests/integration/frameworks/yii/test_basic_cli.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
The agent should name Yii2 cli transactions
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/Yii2/detected
OtherTransaction/Action/test-integration-cli-action
*/

/*EXPECT_ERROR_EVENTS null */

/* Yii2 mock */
require_once __DIR__.'/yii2/baseyii.php';

$a = new yii\base\InlineAction('test-integration-cli-action');
$a->runWithParams([]);
22 changes: 22 additions & 0 deletions tests/integration/frameworks/yii/test_basic_web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
The agent should name Yii2 web transactions
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/Yii2/detected
OtherTransaction/Action/test-integration-web-action
*/

/*EXPECT_ERROR_EVENTS null */

/* Yii2 mock */
require_once __DIR__.'/yii2/baseyii.php';

$a = new yii\base\Action('test-integration-web-action');
$a->runWithParams([]);
46 changes: 46 additions & 0 deletions tests/integration/frameworks/yii/yii2/baseyii.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/* Mock enough bits of Yii2 infrastructure for naming tests. */

namespace yii\base {

/* Web action */
class Action {
private $uniqid;

public function __construct($argument) {
$this->uniqid = $argument;
}

public function getUniqueId ( ) {
return $this->uniqid;
}

public function runWithParams($params) {
return;
}
}

/* Console action */
class InlineAction {
private $uniqid;

public function __construct($id) {
$this->uniqid = $id;
}

public function getUniqueId ( ) {
return $this->uniqid;
}

public function runWithParams($params) {
return;
}
}
echo "";
}

0 comments on commit 539dd4f

Please sign in to comment.