之前有提到,整个WP的运行就是在运行无数的action与filter。这些action与filter,统称为Hook,翻译过来是挂钩、钩子的意思,但一般不翻译直接称Hook。WP预留了无数的Hook,开发者在这些Hook上挂载函数,当WP运行到这个Hook点时,挂载在这个Hook上的函数都被执行。更通俗点的说法,Hook就是WP运行过程中的某个点,比如“wp_header”这个Hook,就是渲染HTML header头部时这个时间点。我们在”wp_header”这个Hook上挂载一个函数来引入CSS文件,当WP运行到这个”wp_header” Hook时就执行挂载的函数,也就将CSS文件引进来了。
对于action与filter这两种Hook,添加的方法是add_action()与add_filter(),执行是do_action()与apply_filters()。action侧重执行这个动作,filter侧重返回结果,一个侧重过程,一个侧重结果。对于挂载到action上的函数,无须返回,而挂载到filter上的函数,必须有返回值,否则会出错,因为WP是要使用返回的结果继续往下运行。
add_action ( string $tag, callback $function_to_add, int $priority = 10, int $accepted_args = 1 ) add_filter ( string $tag, callback $function_to_add, int $priority = 10, int $accepted_args = 1 )
可以看到,二者使用方法是基本一致的,实际其内部实现也是基本一致的。$tag是Hook的名称,$function_to_add是要挂载的函数,$priority是先后顺序,越小表示越靠前越早运行,默认10。$accepted_args是接收的参数,表示$function_to_add可以接收的参数个数,运行时候会按这个数量进行传递,默认是1。
//添加一个acton到xiaomiao Hook上。WP自身不带这个Hook,会自动创建 add_action('xiaomiao', 'action_xiaomiao'); function action_xiaomiao($args) { echo '这是'.__FUNCTION__.":输出$args</br>"; } //WP自身也并不会运行这个Hook,手动运行 do_action('xiaomiao', '自定义action');//要传递多个参数,add_action时要指定,否则运行时并不会被传递
以上代码会输出:
这是action_xiaomiao:输出自定义action
以上就是action Hook的使用例子。这里我们自己创建了一个Hook: xiaomiao,实际开发中大多情况是找WP自带的运行过程中会执行的Hook来挂函数。比如我们删除了一篇文章,之前往文章添加了额外信息存在postmeta表中,我们就需要将额外信息也同时删掉,我们在WP自带的deleted_post Hook上挂载函数,在函数中使用delete_post_meta()方法将额外信息删除即可(WP运行过程中在删除了一篇文章时会执行这个Hook,就运行了我们的删除函数)。其实这跟一些框架中所谓的前置操作(如before_delete)、后置操作(如after_delete)是一样的。
add_action('xiaomiao', 'action_xiaomiao'); function action_xiaomiao($args) { $args = apply_filters('cylx', $args);//经由这个filter处理一翻 echo '这是'.__FUNCTION__.":输出$args</br>"; } function filter_custom($args) { return $args.'----添加个小尾巴'; } add_filter('cylx', 'filter_custom'); do_action('xiaomiao', '自定义action');
以上示例代码输出信息如下:
这是action_xiaomiao:输出自定义action----添加个小尾巴
在这里,我们自定义了一个filter Hook命名为cylx,并往上边挂载了一个函数filter_custom,对传入的结果追加一段字符串。这种场景就像WP中给文章内容末尾追加一个版权说明做法是往the_content这个Hook上挂载函数是一样的。这个跟所谓的装饰器模式其实就是差不多的。
知道了用法,这种机制是如何实现的呢。通过查看add_filter()的代码发现,在WP中用一个全局三维数组$wp_filter来存储所有的Hook(包括action Hook和filter Hook),一层键是Hook的名称,二层键是其优先级,三层则是一个自己生成唯一的id。这个id,当$function_to_add是一个字符串时就是这个字符串(这时表明是一个全局函数,不会有重名)。当不是字符串而是一个逆名函数(逆名函数也是对象)或其他(其实我也不知道还能是什么)时,若有spl_object_hash()存在就直接用,否则又是自己一套,具体的我不是它的开发者也就不清楚剖析了。有兴趣的去看它代码。至于add_action(),其实就是调用一下add_filter(),它俩实际是一回事,估计是为了区分动作Hook与过滤Hook而起了两个名字。
//部分代码 $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority); $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args);

WP默认带了230多个Hook
在执行阶段,apply_filters()和do_action(),将某个$tag的函数按$priority排好序从小到大按顺序执行一遍即可。当然,其中肯定要它特定要处理的细节,我说的只是大体思路。假如我们自己写一个,应该从哪里下手?
首先,执行时候用的是call_user_func_array(callable $callback, array $param_arr)。这个$callback可以接收的参数形式有如下:
$s = function ($a) { echo __FUNCTION__.'---'.$a.'<br/>'; }; function ss($a) { echo __FUNCTION__.'--'.$a.'<br/>'; } class T { public static function t1 ($args) { echo 'static calling:'.$args.'<br/>'; } public function t2 ($args) { echo 'non-static calling:'.$args.'<br/>'; } } call_user_func_array($s, array('abcd'));//{closure}---abcd call_user_func_array('ss', array('abcd'));//ss--abcd call_user_func_array('T::t1', array('abcd'));//static calling:abcd call_user_func_array(array('T', 't1'), array('abcd'));//static calling:abcd call_user_func_array(array(new T, 't2'), array('abcd'));//non-static calling:abcd
因此,这个$callback有可能是这几种情况:
- 一个闭包函数(closure)
- 一个字符串(函数名或类的静态调用)
- 一个数组,元素1是字符串(类名,这时是静态调用)或一个类(类的实例,这时是非静态调用),元素2是字符串(类的方法名)
分别针对以上情况将$callback存起来即可。关键在于针对不同情况创建一个唯一id,这样方便检索一个Hook是否存在,或者删除一个Hook,运行时其实是用不到这个id的。
$my_filter = array(); function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { global $my_filter; if (empty($tag) || empty($function_to_add)) { return FALSE; } $idx = _build_unique_idx($function_to_add); if (empty($idx)) { return FALSE; } $my_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); return TRUE; } function _build_unique_idx($function) { if (is_string($function)) { //普通函数或类静态调用 return $function; } elseif (is_object($function) && ($function instanceof Closure)) { //闭包 return spl_object_hash($function); } elseif (is_array($function)) { if (is_object($function[0])) { //类非静态调用 return spl_object_hash($function[0]).$function[1];//类hash后的值连上方法名 } elseif (is_string($function[0])) { //类静态调用 return $function[0].'::'.$function[1]; } } return FALSE; }
以上是参考WP实现的粗略的add_filter()。add_action()也就是调用一下add_filter()而已。执行的apply_filters()如下:
function apply_filters($tag, $value) { global $my_filter; if (!isset($my_filter[$tag])) { return NULL; } ksort($my_filter[$tag]);//按优先级高到低排序(数值越小优先级越高) reset($my_filter[$tag]);//重置指针 $args = func_get_args();//定义时不能确定参数个数,$args中的第0个为$tag do { foreach ((array)current($my_filter[$tag]) as $_the)//通过next移动指针,current取当前值 { $args[1] = $value;//将$args中的第1个设置为$value累积下去 //参数个数进行控制,默认1个。 $value = call_user_func_array($_the['function'], array_slice($args, 1, $_the['accepted_args'])); } } while (next($my_filter[$tag]) !== FALSE); return $value; }
至于do_action()大同小异,do_action不需要累积结果,只需要执行一次就行了。略过。测试自己写的add_filter()&apply_filters()如下:
$s = function ($a) { $a .= __FUNCTION__.'进行了加工--'; echo $a.'<hr/>'; return $a; }; function ss($a) { $a .= __FUNCTION__.'进行了加工--'; echo $a.'<hr/>'; return $a; } class T { public static function t1 ($args) { $args .= __CLASS__.'->'.__FUNCTION__.'进行了加工--'; echo $args.'<hr/>'; return $args; } public function t2 ($args) { $args .= __CLASS__.'->'.__FUNCTION__.'进行了加工--'; echo $args.'<hr/>'; return $args; } } add_filter('test', $s); add_filter('test', 'ss'); add_filter('test', array('T', 't1')); add_filter('test', array(new T, 't2')); echo '<pre>'; $r = apply_filters('test', 'a'); echo '最终结果是:'; var_dump($r);
输出结果如下: