@@ -625,36 +625,77 @@ static bool nr_is_invalid_key_val_arr(nr_php_string_hash_key_t* key,
625625}
626626
627627/*
628- * Purpose: Instrument Drupal Attribute Hooks for Drupal 11.1+
628+ * Extract all hook functions and hook data for the drupal 11.1-11.2
629+ * application
629630 *
630- * Params: 1. A zval pointer to the moduleHandler instance in use by Drupal.
631+ * @param zval* hookmap: a valid, non-null pointer to the zval containing the
632+ * ModuleHandler `hookImplementationsMap` class property.
631633 *
632- * Return: bool
634+ * @return bool: true for success, false for failure
635+ *
636+ * Drupal 11.1 introduces the `hookImplementationsMap` property on the
637+ * ModuleHandler class. This map contains all the hooks registered for the
638+ * current drupal application.
639+ *
640+ * The structure of `hookImplementationsMap` resembles the following:
633641 *
642+ * array(348) { <------ the `hookImplementationsMap` property array
643+ * ["hook_info"]=> <------ a subarray keyed by hook name
644+ * array(1) {
645+ * ["Drupal\Core\Extension\ProceduralCall"]=> <- an array keyed by class name
646+ * array(2) {
647+ * ["system_hook_info"]=> <----- the function name implementing the hook
648+ * string(6) "system" <----- the module name
649+ * ["views_hook_info"]=>
650+ * string(5) "views"
651+ * }
652+ * }
653+ * ...
654+ *
655+ * The `ProceduralCall` class name is a special case where the classname is not
656+ * required to call the hook and should be left out of the hookpath used to name
657+ * the hook.
658+ *
659+ * An example of the `help` hook:
660+ *
661+ * ["help"]=>
662+ * array(51) {
663+ * ["Drupal\announcements_feed\Hook\AnnouncementsFeedHooks"]=>
664+ * array(1) {
665+ * ["help"]=>
666+ * string(18) "announcements_feed"
667+ * }
668+ * ["Drupal\automated_cron\Hook\AutomatedCronHooks"]=>
669+ * array(1) {
670+ * ["help"]=>
671+ * string(14) "automated_cron"
672+ * }
673+ * ["Drupal\big_pipe\Hook\BigPipeHooks"]=>
674+ * array(1) {
675+ * ["help"]=>
676+ * string(8) "big_pipe"
677+ * }
678+ * ["Drupal\block\Hook\BlockHooks"]=>
679+ * array(1) {
680+ * ["help"]=>
681+ * string(5) "block"
682+ * }
683+ * ...
684+ *
685+ * To provide hook instrumentation, we loop through all the elements of the
686+ * array and extract the hook key name, class key name, function key name and
687+ * module name, and construct a wrapper using that data for each hook.
634688 */
635- static bool nr_drupal_hook_attribute_instrument (zval * module_handler ) {
636- zval * hook_implementation_map = NULL ;
637-
689+ static bool nr_drupal_parse_hookmap (zval * hookmap ) {
638690 nr_php_string_hash_key_t * hook_key = NULL ;
639691 zval * hook_val = NULL ;
640692 nr_php_string_hash_key_t * class_key = NULL ;
641693 zval * class_val = NULL ;
642694 nr_php_string_hash_key_t * method_key = NULL ;
643695 zval * module_val = NULL ;
644-
645696 char * hookpath = NULL ;
646697
647- hook_implementation_map = nr_php_get_zval_object_property (
648- module_handler , "hookImplementationsMap" );
649-
650- if (!nr_php_is_zval_valid_array (hook_implementation_map )) {
651- nrl_verbosedebug (NRL_FRAMEWORK ,
652- "hookImplementationsMap property not a valid array" );
653- return false;
654- }
655-
656- ZEND_HASH_FOREACH_STR_KEY_VAL (Z_ARRVAL_P (hook_implementation_map ), hook_key ,
657- hook_val ) {
698+ ZEND_HASH_FOREACH_STR_KEY_VAL (Z_ARRVAL_P (hookmap ), hook_key , hook_val ) {
658699 if (nr_is_invalid_key_val_arr (hook_key , hook_val , "hook" )) {
659700 return false;
660701 }
@@ -704,6 +745,138 @@ static bool nr_drupal_hook_attribute_instrument(zval* module_handler) {
704745 return true;
705746}
706747
748+ /*
749+ * Extract all hook functions and hook data for the drupal 11.3+ application
750+ *
751+ * @param zval* hookList: a valid, non-null pointer to the zval containing the
752+ * ModuleHandler `hookLists` class property.
753+ *
754+ * @return bool: true for success, false for failure
755+ *
756+ * Drupal 11.3+ again changes the internal model of how hooks are stored
757+ * and represented from prior versions. The `hookImplementationsMap`
758+ * property introduced in Drupal 11.1 is replaced with `hookLists`, and the
759+ * internal representation of the hook data is flattened compared to 11.1.
760+ *
761+ * The structure of `hookLists` resembles the following:
762+ *
763+ * array(244) { <------ the `hookLists` property array
764+ * ["hook_info"]=> <------ a subarray keyed by hook name
765+ * array(2) {
766+ * ["system_hook_info"]=> <------ the function name implementing the hook
767+ * string(6) "system" <------ the module name
768+ * ["views_hook_info"]=>
769+ * string(5) "views"
770+ * }
771+ * ...
772+ *
773+ * An example of the `help` hook where the full classpath is contained in the
774+ * function key:
775+ *
776+ * ["help"]=>
777+ * array(50) {
778+ * ["Drupal\announcements_feed\Hook\AnnouncementsFeedHelpHooks::help"]=>
779+ * string(18) "announcements_feed"
780+ * ["Drupal\automated_cron\Hook\AutomatedCronHooks::help"]=>
781+ * string(14) "automated_cron"
782+ * ["Drupal\big_pipe\Hook\BigPipeHooks::help"]=>
783+ * string(8) "big_pipe"
784+ * ["Drupal\block\Hook\BlockHooks::help"]=>
785+ * string(5) "block"
786+ * ...
787+ *
788+ * To provide hook instrumentation, we loop through all the elements of the
789+ * array and extract the hook key name, function key name, and module name,
790+ * and construct a wrapper using that data for each hook.
791+ */
792+ static bool nr_drupal_parse_hooklist (zval * hooklist ) {
793+ nr_php_string_hash_key_t * hook_key = NULL ;
794+ zval * hook_val = NULL ;
795+ nr_php_string_hash_key_t * func_key = NULL ;
796+ zval * module_val = NULL ;
797+
798+ ZEND_HASH_FOREACH_STR_KEY_VAL (Z_ARRVAL_P (hooklist ), hook_key , hook_val ) {
799+ if (nr_is_invalid_key_val_arr (hook_key , hook_val , "hook" )) {
800+ return false;
801+ }
802+ ZEND_HASH_FOREACH_STR_KEY_VAL (Z_ARRVAL_P (hook_val ), func_key , module_val ) {
803+ if (NULL == func_key ) {
804+ nrl_verbosedebug (NRL_FRAMEWORK ,
805+ "hookLists[hook][func]: NULL key for hook: %s" ,
806+ NRSAFESTR (ZEND_STRING_VALUE (hook_key )));
807+ return false;
808+ }
809+ if (NULL == module_val ) {
810+ nrl_verbosedebug (
811+ NRL_FRAMEWORK ,
812+ "hookLists[hook][func]: NULL module for func %s on hook %s" ,
813+ NRSAFESTR (ZEND_STRING_VALUE (func_key )),
814+ NRSAFESTR (ZEND_STRING_VALUE (hook_key )));
815+ return false;
816+ }
817+ if (0 == nr_php_is_zval_valid_string (module_val )) {
818+ nrl_verbosedebug (
819+ NRL_FRAMEWORK ,
820+ "hookLists: non-string module value for func %s on hook %s" ,
821+ NRSAFESTR (ZEND_STRING_VALUE (func_key )),
822+ NRSAFESTR (ZEND_STRING_VALUE (hook_key )));
823+ return false;
824+ }
825+
826+ nrl_verbosedebug (
827+ NRL_FRAMEWORK ,
828+ "hookLists: creating wrapper for hook: %s func: %s module: %s" ,
829+ NRSAFESTR (ZEND_STRING_VALUE (hook_key )),
830+ NRSAFESTR (ZEND_STRING_VALUE (func_key )), Z_STRVAL_P (module_val ));
831+
832+ nr_php_wrap_user_function_drupal (
833+ ZEND_STRING_VALUE (func_key ), ZEND_STRING_LEN (func_key ),
834+ Z_STRVAL_P (module_val ), Z_STRLEN_P (module_val ),
835+ ZEND_STRING_VALUE (hook_key ), ZEND_STRING_LEN (hook_key ));
836+ }
837+ ZEND_HASH_FOREACH_END ();
838+ }
839+ ZEND_HASH_FOREACH_END ();
840+
841+ return true;
842+ }
843+
844+ /*
845+ * Purpose: Instrument Drupal Attribute Hooks for Drupal 11.1+
846+ *
847+ * Params: 1. A zval pointer to the moduleHandler instance in use by Drupal.
848+ *
849+ * Return: bool
850+ *
851+ */
852+ static bool nr_drupal_hook_attribute_instrument (zval * module_handler ) {
853+ zval * hook_implementation_map = NULL ;
854+
855+ bool uses_hooklist = false;
856+
857+ hook_implementation_map = nr_php_get_zval_object_property (
858+ module_handler , "hookImplementationsMap" );
859+
860+ if (!nr_php_is_zval_valid_array (hook_implementation_map )) {
861+ hook_implementation_map
862+ = nr_php_get_zval_object_property (module_handler , "hookLists" );
863+ if (!nr_php_is_zval_valid_array (hook_implementation_map )) {
864+ nrl_verbosedebug (NRL_FRAMEWORK ,
865+ "unable to extract hook array from ModuleHandler class" );
866+ return false;
867+ }
868+ uses_hooklist = true;
869+ }
870+
871+ if (uses_hooklist ) {
872+ nr_drupal_parse_hooklist (hook_implementation_map );
873+ } else {
874+ nr_drupal_parse_hookmap (hook_implementation_map );
875+ }
876+
877+ return true;
878+ }
879+
707880/*
708881 * Purpose : Wrap the invoke() method of the module handler instance in use.
709882 */
0 commit comments