22. 钩子、模块处理器、函数定义
钩子:
如果是开发新手可能对这个慨念陌生而好奇,许多工作多年的工程师可能对它也缺乏深度理解,不止是在php中,它其实是软件工程中的重要慨念,什么意思呢?为什么要叫做钩子?既然叫做钩子直觉的就会联想到钩什么?怎么钩?简单而直接的回答就是在软件运行的某时刻去钩起一段代码来执行,这个“钩起”也可以称为调用、获取等,但总不能叫做“调子”“获子”吧,所以形象起见叫做钩子,可以将钩子理解为一种软件实现机制,函数调用就是钩子最简单的情况,执行函数时调用它就是钩起它,函数就是钩子实现,函数名就是钩子名,怎么钩就是通过函数名来钩。
以上是对钩子宏观的理解,有了这个慨念后来看一看drupal中钩子的概念:
在drupal系统中钩子是一种非常重要的互动机制,系统通过它在系统和模块间、模块和模块间互动,通过特别命名的函数来实现,具体说就是执行流程中在某一个点去执行满足某特定命名的函数,可能是一个函数也可能是一些函数,其他部分可以定义这些函数,在这里“特定命名”这个词就很重要了!drupal是怎么特定这个命名的呢?在实现中为某种功能执行取个名字(流程中某个点会执行这个功能)就是钩子名,各模块可以自定义一个函数,函数名为“模块名_钩子名”,这些函数有共同特性:为完成同一性质的事情而定义(也就是前文说的某功能执行),在流程中的某点一起依次执行它们,模块需要参与就定义相应函数,反之则不定义,在php中函数是超全局的,在任何地方都可以调用。钩子是不是和软件设计中的事件订阅设计模式类似呢?没错,本质上它们是一回事,钩子实现中在执行点调用钩子函数集这个动作就相当于派发了一个事件,实现了满足钩子实现命名的函数就相当于订阅了这个事件!
在drupal8中系统已经定义了许多钩子名,它们在系统运行的各个点被执行,这样允许模块参与到核心中来,下文将列出默认安装时的所有钩子名。模块只要实现了满足钩子命名的函数,这些函数将在钩子调用时执行,通常将这些函数叫做钩子实现。模块也可以自定义钩子名。
元旦刚刚过去,继续保持一周一篇的速度,这是Drupal8云客源码分析系列在2017年的开篇,来讲几个极其重要的内容,它们是钩子、模块处理器、函数定义。
模块怎么定义一个钩子名呢?
比如模块名叫“yunke”,要定义一个钩子(名)叫做“sendMail”那么在模块的主目录中建立一个名为“yunke.api.php”的文件,里面定义一个样板函数叫做:hook_sendMail(...),加上注释,说明在yunke这个模块中怎么运用这个钩子的,然后在yunke这个模块的代码中通过模块处理器(下文讲)来执行这个钩子,其他模块实现了这个钩子就会被yunke这个模块执行到,其实“yunke.api.php”这个文件是不会被系统执行的,它的作用完全形同一个文档,指导其他模块开发者去实现钩子,在系统中你可以看到许多这样的文件,他们位于模块的主目录,在drupal官方的API文档站点就是通过自动解析它们来获取钩子注释文档的,所以样板函数和注释越详细越好,它的作用就是一个文档。
模块怎么实现上面定义的sendMail钩子呢?定义一个函数,将函数名“hook_sendMail”中的hook换为自己的模块名即可,函数放置在.module文件中,比如实现这个钩子的模块叫做“yunke_php”,那么就放置在主目录的yunke_php. module文件中。
关于钩子实现函数的文件位置还有另外一种放置方法:定义这个钩子的模块可以定义一个钩子信息函数,放置在.module文件中(比如上文就是yunke .module),函数名格式为:"模块名_hook_info",如上文就是“yunke _hook_info”,它通过返回值来申明钩子实现可以放置的位置,返回一个数组,键名为钩子名,对应值为一个数组:Array ("group"=> $var),以前文的yunke模块为例:钩子信息函数名为:“yunke_hook_info”,如果他返回内容如下:
$hooks[' sendMail '] = array('group' => 'user');
那么其他模块对sendMail这个钩子的实现还可以放置在自己主目录的“模块名.user.inc”文件里,这样的设计是为了将很少使用、很庞大的钩子独立放置在$module.$group.inc文件中,不需要时可不加载,提高性能,模块的“.module”文件默认是每个请求都全部加载的。关于钩子信息函数"模块名_hook_info"的具体实现可以参考:\core\modules\system\system.module中的system_hook_info()
模块提供的钩子信息函数会被模块处理器执行,结果合并后存放在缓存中以备后用。
明白了钩子的概念,就不难理解系统的模块处理器了,继续。
模块处理器:
模块处理器是容器中的一个服务,服务名为“module_handler”,在任何地方均可以通过\Drupal::moduleHandler()得到,它负责保存系统开启的模块信息、加载模块的函数、执行钩子等,在系统中非常多的地方都用到了它,可以说它是系统和模块、模块和模块之间的重要桥梁之一。
文件位于:\core\lib\Drupal\Core\Extension\ModuleHandler.php
它有三个构造参数:
$root(app.root):
是一个SplString对象,该对象代表一个字符串值,是系统的根目录,此值也保存在常量DRUPAL_ROOT中
$module_list(container.modules):
系统开启的模块列表,是容器参数,在容器构建时产生
$cache_backend(cache.bootstrap)
是一个缓存,默认实现中从数据库bootstrap缓存表获取数据
在drupal中模块类型有两种:module(普通模块)、profile(配置模块)
在系统初始阶段(容器形成后的核心堆栈中间层里)将调用模块处理器加载所有开启模块的“.module”文件,这个文件相当于模块用到的函数库。
模块处理器的实现比较简单,这里只列出一些常用或要点来介绍:
加载模块的其他文件:
\Drupal::moduleHandler()->loadInclude($module, $type, $name = NULL)
加载模块$module主目录下文件名为$name扩展名为$type的文件,模块必须是开启的,文件名为空则用模块名代替,这常用于加载函数库,当然也可以是其他文件
系统默认有两个模块定义的钩子信息函数:
system_hook_info 位于:\core\modules\system\ system.module
views_hook_info 位于\core\modules\views\ views.module
他们的返回结果被合并后存放于缓存表bootstrap的hook_info缓存项中
(代码见辅助内容区)
模块提供的钩子在模块的*.api.php file中介绍,那是快速参考文档
修改钩子的执行顺序:
可以实现module_implements_alter钩子,用它来修改钩子的执行顺序
执行钩子:
\Drupal::moduleHandler()->invoke($module, $hook, array $args = array()) 执行某模块实现的钩子
\Drupal::moduleHandler()->invokeAll($hook, array $args = array()) 执行系统所有的$hook钩子
\Drupal::moduleHandler()->alter($type, &$data, &$context1 = NULL, &$context2 = NULL) 执行修改钩,它是建立在钩子之上的功能,传递参数时可以省略钩子的_alter后缀
容易混淆的名字问题:
模块有内部使用的机器名和用于给人类显示的人类可读名,机器名并不是文件夹名,而是info.yml文件的文件名(不带.info.yml后缀),所以文件命名要符合php变量命名规则,文件夹名可以和文件名不一致,人类可读名申明在info.yml文件里的name字段,可使用下列代码得到:
\Drupal::moduleHandler()->getName($module)
<span style="font-size:14px;">function system_hook_info() {
$hooks['token_info'] = array(
'group' => 'tokens',
);
$hooks['token_info_alter'] = array(
'group' => 'tokens',
);
$hooks['tokens'] = array(
'group' => 'tokens',
);
$hooks['tokens_alter'] = array(
'group' => 'tokens',
);
return $hooks;
}
</span>
函数放置:
要进行drupal开发,开发者首要关心的可能就是函数库的放置,他们应该放在哪个文件里面呢?
通常我们是以模块的方式向drupal系统添加文件,所以首先可以将函数放置在模块主目录下的“模块名.module”文件中,该文件会被系统在容器建立后被自动加载
钩子函数不但可以放置在“模块名.module”文件中还可以放置在$module.$group.inc文件中,详见上文
也可以放置在模块主目录下的任意文件中,但不会被系统自动加载,需要使用以下代码完成加载:
\Drupal::moduleHandler()->loadInclude($module, $type, $name = NULL);
它表示加载模块$module主目录下文件名为$name扩展名为$type的文件,文件名为空则用模块名代替,这常用于加载函数库,当然也可以是其他文件
注意放置在模块中的函数,通过以上方法加载均有一个前提:模块是启用的!这非常重要
如果函数无关任何模块,也不需要是否启用模块均要求全站公用怎么处理呢?
可以在站点配置文件(默认是\sites\default\ settings.php)中include进来,路径中可以使用常量DRUPAL_ROOT,它表示drupal安装根目录的绝对路径,声明在/core/includes/bootstrap.inc中,比如:
include_once DRUPAL_ROOT."/yunkelib.php"; //注意不要忘记反斜线
这样就可以任意加载自己的函数库了,不受任何限制。
这里需要注意:在任何时候都不要去修改core里面的内容,否则升级时将可能有麻烦
按照函数加载的时间顺序,有一个优先级,优先级越高越先加载,如下:
配置文件加载>“模块名.module”> loadInclude函数
php基础知识提示:php文件可以使用任意扩展名,包含进来时将只关注它的内容,不会在意它的扩展名,这也是系统中许多函数可以放置在非php后缀的原因
详见php官网http://php.net/manual/zh/function.include.php
附件:列出默认安装中钩子的实现
在缓存中保存了钩子的默认实现和钩子信息:
module_implements.bin :钩子的实现信息 键名为钩子名,键值为一个数组,该数组键名为实现了这个钩子的模块名,对应值为位置$group信息,如果没有$group则为false
Array
(
[module_implements_alter] => Array
(
)
[entity_load] => Array
(
)
[view_load] => Array
(
)
[language_types_info] => Array
(
)
[language_types_info_alter] => Array
(
[language] =>
)
[views_pre_view] => Array
(
)
[views_plugins_sort_alter] => Array
(
)
[views_plugins_filter_alter] => Array
(
)
[views_plugins_area_alter] => Array
(
)
[views_pre_build] => Array
(
)
[views_plugins_query_alter] => Array
(
)
[views_query_alter] => Array
(
)
[views_query_substitutions] => Array
(
[node] => views_execution
[user] => views_execution
[views] => views_execution
)
[views_post_build] => Array
(
)
[views_pre_execute] => Array
(
)
[views_plugins_cache_alter] => Array
(
)
[query_alter] => Array
(
)
[query_views_alter] => Array
(
[views] =>
)
[query_views_frontpage_alter] => Array
(
)
[node_grants] => Array
(
)
[query_node_access_alter] => Array
(
[node] =>
)
[query_node_load_multiple_alter] => Array
(
)
[entity_storage_load] => Array
(
[comment] =>
)
[node_storage_load] => Array
(
)
[node_load] => Array
(
)
[views_post_execute] => Array
(
)
[entity_view_mode_alter] => Array
(
)
[node_build_defaults_alter] => Array
(
)
[entity_build_defaults_alter] => Array
(
)
[views_pre_render] => Array
(
[views] => views_execution
)
[views_post_render] => Array
(
)
[theme_suggestions_views_view] => Array
(
)
[theme_suggestions_alter] => Array
(
)
[theme_suggestions_views_view_alter] => Array
(
)
[template_preprocess_default_variables_alter] => Array
(
[user] =>
)
[theme_suggestions_views_view_unformatted] => Array
(
)
[theme_suggestions_views_view_unformatted_alter] => Array
(
)
[entity_extra_field_info] => Array
(
[comment] =>
[contact] =>
[node] =>
[user] =>
)
[comment_type_load] => Array
(
)
[node_type_load] => Array
(
)
[entity_extra_field_info_alter] => Array
(
)
[field_formatter_info_alter] => Array
(
[editor] =>
[quickedit] =>
)
[entity_view_display_load] => Array
(
)
[entity_view_display_alter] => Array
(
[node] =>
)
[entity_prepare_view] => Array
(
[rdf] =>
)
[rdf_mapping_load] => Array
(
)
[entity_field_access] => Array
(
[language] =>
)
[entity_field_access_alter] => Array
(
)
[query_user_load_multiple_alter] => Array
(
)
[user_storage_load] => Array
(
)
[user_load] => Array
(
)
[date_format_load] => Array
(
)
[entity_display_build_alter] => Array
(
)
[node_view] => Array
(
)
[entity_view] => Array
(
[comment] =>
)
[node_view_alter] => Array
(
[comment] =>
[history] =>
)
[entity_view_alter] => Array
(
[quickedit] =>
)
[theme_suggestions_node] => Array
(
[node] =>
)
[theme_suggestions_node_alter] => Array
(
[views] =>
)
[theme_suggestions_field] => Array
(
[system] =>
)
[theme_suggestions_field_alter] => Array
(
)
[theme_suggestions_username] => Array
(
)
[theme_suggestions_username_alter] => Array
(
)
[user_format_name_alter] => Array
(
)
[file_url_alter] => Array
(
)
[user_build_defaults_alter] => Array
(
)
[theme_suggestions_rdf_metadata] => Array
(
)
[theme_suggestions_rdf_metadata_alter] => Array
(
)
[user_view] => Array
(
[user] =>
)
[user_view_alter] => Array
(
[user] =>
)
[theme_suggestions_user] => Array
(
)
[theme_suggestions_user_alter] => Array
(
)
[filter_format_load] => Array
(
)
[filter_info_alter] => Array
(
)
[node_links_alter] => Array
(
[comment] =>
)
[query_comment_access_alter] => Array
(
)
[query_entity_query_alter] => Array
(
)
[query_entity_query_comment_alter] => Array
(
)
[theme_suggestions_links] => Array
(
)
[theme_suggestions_links_alter] => Array
(
)
[link_alter] => Array
(
)
[theme_suggestions_pager] => Array
(
)
[theme_suggestions_pager_alter] => Array
(
)
[theme_suggestions_feed_icon] => Array
(
)
[theme_suggestions_feed_icon_alter] => Array
(
)
[theme_suggestions_container] => Array
(
)
[theme_suggestions_container_alter] => Array
(
[views] =>
)
[block_load] => Array
(
)
[entity_access] => Array
(
)
[block_access] => Array
(
)
[block_build_alter] => Array
(
)
[block_build_system_branding_block_alter] => Array
(
)
[block_build_system_menu_block_alter] => Array
(
)
[block_build_system_messages_block_alter] => Array
(
)
[block_build_system_breadcrumb_block_alter] => Array
(
)
[block_build_page_title_block_alter] => Array
(
)
[block_view_alter] => Array
(
)
[block_view_page_title_block_alter] => Array
(
)
[block_build_local_tasks_block_alter] => Array
(
)
[block_build_help_block_alter] => Array
(
)
[block_build_local_actions_block_alter] => Array
(
)
[block_build_system_main_block_alter] => Array
(
)
[block_view_system_main_block_alter] => Array
(
[system] =>
)
[block_build_search_form_block_alter] => Array
(
)
[block_build_system_powered_by_block_alter] => Array
(
)
[page_attachments] => Array
(
[contextual] =>
[quickedit] =>
[system] =>
)
[page_attachments_alter] => Array
(
[taxonomy] =>
)
[page_top] => Array
(
[block] =>
[node] =>
[toolbar] =>
[update] =>
)
[page_bottom] => Array
(
[tour] =>
)
[theme_suggestions_html] => Array
(
[system] =>
)
[theme_suggestions_html_alter] => Array
(
)
[rdf_namespaces] => Array
(
[rdf] =>
)
[theme_suggestions_page] => Array
(
[system] =>
)
[theme_suggestions_page_alter] => Array
(
)
[theme_suggestions_region] => Array
(
[system] =>
)
[theme_suggestions_region_alter] => Array
(
)
[theme_suggestions_block] => Array
(
[block] =>
)
[theme_suggestions_block_alter] => Array
(
)
[theme_suggestions_page_title] => Array
(
)
[theme_suggestions_page_title_alter] => Array
(
)
[shortcut_default_set] => Array
(
)
[shortcut_set_load] => Array
(
)
[query_shortcut_access_alter] => Array
(
)
[query_entity_query_shortcut_alter] => Array
(
)
[shortcut_load] => Array
(
)
[block_view_local_tasks_block_alter] => Array
(
)
[menu_local_tasks_alter] => Array
(
[contact] =>
)
[block_view_help_block_alter] => Array
(
[help] =>
)
[help] => Array
(
[automated_cron] =>
[ban] =>
[block] =>
[block_content] =>
[breakpoint] =>
[ckeditor] =>
[color] =>
[comment] =>
[config] =>
[contact] =>
[contextual] =>
[datetime] =>
[dblog] =>
[dynamic_page_cache] =>
[editor] =>
[field] =>
[field_ui] =>
[file] =>
[filter] =>
[help] =>
[history] =>
[image] =>
[language] =>
[link] =>
[locale] =>
[menu_ui] =>
[node] =>
[options] =>
[page_cache] =>
[path] =>
[quickedit] =>
[rdf] =>
[search] =>
[shortcut] =>
[system] =>
[taxonomy] =>
[text] =>
[toolbar] =>
[tour] =>
[update] =>
[user] =>
[views_ui] =>
[menu_link_content] =>
[views] =>
)
[block_view_local_actions_block_alter] => Array
(
)
[theme_suggestions_status_messages] => Array
(
)
[theme_suggestions_status_messages_alter] => Array
(
)
[library_info_alter] => Array
(
[ckeditor] =>
[color] =>
[locale] =>
[quickedit] =>
)
[css_alter] => Array
(
)
[js_alter] => Array
(
[locale] =>
)
[js_settings_build] => Array
(
[system] =>
)
[js_settings_alter] => Array
(
[locale] =>
[system] =>
[user] =>
)
[contextual_links_alter] => Array
(
)
[menu_load] => Array
(
)
[menu_access] => Array
(
)
[contextual_links_view_alter] => Array
(
[contextual] =>
[views_ui] =>
)
[view_access] => Array
(
)
[quickedit_editor_alter] => Array
(
)
[ajax_render_alter] => Array
(
)
)
hook_info.bin:钩子信息函数返回的合并结果,键名为钩子名,键值为一个数组,group代表该钩子实现还可以存放在$module.$group.inc文件中
Array
(
[token_info] => Array
(
[group] => tokens
)
[token_info_alter] => Array
(
[group] => tokens
)
[tokens] => Array
(
[group] => tokens
)
[tokens_alter] => Array
(
[group] => tokens
)
[views_data] => Array
(
[group] => views
)
[views_data_alter] => Array
(
[group] => views
)
[views_analyze] => Array
(
[group] => views
)
[views_invalidate_cache] => Array
(
[group] => views
)
[views_plugins_access_alter] => Array
(
[group] => views
)
[views_plugins_area_alter] => Array
(
[group] => views
)
[views_plugins_argument_alter] => Array
(
[group] => views
)
[views_plugins_argument_default_alter] => Array
(
[group] => views
)
[views_plugins_argument_validator_alter] => Array
(
[group] => views
)
[views_plugins_cache_alter] => Array
(
[group] => views
)
[views_plugins_display_extender_alter] => Array
(
[group] => views
)
[views_plugins_display_alter] => Array
(
[group] => views
)
[views_plugins_exposed_form_alter] => Array
(
[group] => views
)
[views_plugins_field_alter] => Array
(
[group] => views
)
[views_plugins_filter_alter] => Array
(
[group] => views
)
[views_plugins_join_alter] => Array
(
[group] => views
)
[views_plugins_pager_alter] => Array
(
[group] => views
)
[views_plugins_query_alter] => Array
(
[group] => views
)
[views_plugins_relationship_alter] => Array
(
[group] => views
)
[views_plugins_row_alter] => Array
(
[group] => views
)
[views_plugins_sort_alter] => Array
(
[group] => views
)
[views_plugins_style_alter] => Array
(
[group] => views
)
[views_plugins_wizard_alter] => Array
(
[group] => views
)
[views_query_substitutions] => Array
(
[group] => views_execution
)
[views_form_substitutions] => Array
(
[group] => views_execution
)
[views_pre_view] => Array
(
[group] => views_execution
)
[views_pre_build] => Array
(
[group] => views_execution
)
[views_post_build] => Array
(
[group] => views_execution
)
[views_pre_execute] => Array
(
[group] => views_execution
)
[views_post_execute] => Array
(
[group] => views_execution
)
[views_pre_render] => Array
(
[group] => views_execution
)
[views_post_render] => Array
(
[group] => views_execution
)
[views_query_alter] => Array
(
[group] => views_execution
)
[field_views_data] => Array
(
[group] => views
)
[field_views_data_alter] => Array
(
[group] => views
)
)