Home » Code » WordPress的核心——Action与Filter

WordPress的核心——Action与Filter

之前有提到,整个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

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);

输出结果如下:

wp_add_filter

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.