各位靓仔靓女,早上好!今天咱们来扒一扒 WordPress 源码里一个挺有意思的小东西:_wp_filter_build_unique_id()
函数,看看它是怎么给匿名函数和闭包生成唯一ID的。
很多时候,我们在用 add_filter()
和 add_action()
注册钩子的时候,喜欢偷懒,直接用匿名函数,就像这样:
add_filter('the_content', function($content) {
return $content . '<p>我是偷偷加进去的!</p>';
});
或者用更高级的闭包:
$prefix = '偷偷的前缀:';
add_filter('the_title', function($title) use ($prefix) {
return $prefix . $title;
});
问题来了,WordPress 怎么区分这些匿名函数和闭包,并且保证它们不会被重复添加呢? 答案就在 _wp_filter_build_unique_id()
这个函数里。咱们一起深入看看它到底做了些什么。
_wp_filter_build_unique_id()
函数概览
首先,我们来快速浏览一下这个函数的源码(基于 WordPress 6.x 版本):
/**
* Builds a unique ID for the filter.
*
* @since 2.2.3
* @access private
*
* @param string $tag Filter name.
* @param callable $function_to_add Function to be called when the filter is applied.
* @param int $priority Priority of the filter.
*
* @return string Unique ID for usage in internal arrays.
*/
function _wp_filter_build_unique_id( $tag, $function_to_add, $priority ) {
static $filter_id_count = 0;
if ( is_string( $function_to_add ) ) {
return $function_to_add;
}
if ( is_object( $function_to_add ) ) {
// Closures are currently implemented as objects.
$function_to_add = array( $function_to_add, '' );
} else {
$function_to_add = (array) $function_to_add;
}
if ( is_object( $function_to_add[0] ) ) {
// Object Class Calling
if ( function_exists( 'spl_object_hash' ) ) {
return spl_object_hash( $function_to_add[0] ) . $function_to_add[1];
} else {
$filter_id_count ++;
return spl_object_hash( $function_to_add[0] ) . $function_to_add[1] . '_' . $filter_id_count;
}
} elseif ( is_string( $function_to_add[0] ) ) {
// Static Calling
return $function_to_add[0] . '::' . $function_to_add[1];
}
}
是不是感觉有点乱? 没关系,咱们一步一步拆解。
函数功能:生成唯一 ID
这个函数的主要作用就是根据传入的参数($tag
,$function_to_add
,$priority
)生成一个唯一的 ID。这个 ID 会被用来在 WordPress 内部存储和管理过滤器。
$tag
: 钩子的名称,例如'the_content'
,'the_title'
。$function_to_add
: 你要添加的函数或者方法。$priority
: 钩子的优先级。
这个函数返回一个字符串,这个字符串就是生成的唯一 ID。
代码分解
-
静态变量
$filter_id_count
static $filter_id_count = 0;
这个静态变量用于在
spl_object_hash
不可用时生成唯一 ID,主要目的是避免不同匿名函数返回相同的 hash 值。 -
判断
$function_to_add
的类型首先,函数会检查
$function_to_add
的类型,根据不同的类型采取不同的策略来生成 ID。-
字符串(String)
if ( is_string( $function_to_add ) ) { return $function_to_add; }
如果
$function_to_add
是一个字符串,那说明它是一个普通的函数名,直接返回函数名就可以了。例如:add_filter('the_content', 'my_custom_filter'); // 在 _wp_filter_build_unique_id() 中: // $function_to_add 的值是 'my_custom_filter' // 返回值也是 'my_custom_filter'
-
对象(Object)
if ( is_object( $function_to_add ) ) { // Closures are currently implemented as objects. $function_to_add = array( $function_to_add, '' ); }
如果
$function_to_add
是一个对象,那说明它是一个闭包。 由于闭包在 PHP 内部被实现为对象,所以这里将对象转换成数组,方便后续处理。 数组的第一个元素是对象本身,第二个元素是空字符串。$prefix = '偷偷的前缀:'; add_filter('the_title', function($title) use ($prefix) { return $prefix . $title; }); // 在 _wp_filter_build_unique_id() 中: // $function_to_add 的值是一个 Closure 对象 // 被转换成 array(Closure object, '')
-
其他(数组)
else { $function_to_add = (array) $function_to_add; }
如果
$function_to_add
既不是字符串也不是对象,那么就把它强制转换成数组。这种情况通常发生在你要调用一个类的方法时。class MyClass { public function my_method($content) { return $content . '<p>我是类方法!</p>'; } } $my_object = new MyClass(); add_filter('the_content', array($my_object, 'my_method')); // 在 _wp_filter_build_unique_id() 中: // $function_to_add 的值是 array(MyClass object, 'my_method')
-
-
处理对象方法和静态方法
接下来,函数会判断数组的第一个元素(
$function_to_add[0]
)的类型,进一步区分是对象方法还是静态方法。-
对象方法(Object Method)
if ( is_object( $function_to_add[0] ) ) { // Object Class Calling if ( function_exists( 'spl_object_hash' ) ) { return spl_object_hash( $function_to_add[0] ) . $function_to_add[1]; } else { $filter_id_count ++; return spl_object_hash( $function_to_add[0] ) . $function_to_add[1] . '_' . $filter_id_count; } }
如果数组的第一个元素是一个对象,那说明你要调用的是一个对象的方法。 这里会使用
spl_object_hash()
函数来获取对象的唯一哈希值,然后将哈希值和方法名拼接起来,作为唯一 ID。spl_object_hash()
: 这个函数是 PHP 内置的,可以为每个对象生成一个唯一的哈希值。它的优点是速度快,而且生成的哈希值在对象的生命周期内保持不变。$filter_id_count
的作用: 如果spl_object_hash()
不存在(例如,在一些老版本的 PHP 环境中),则使用静态变量$filter_id_count
来辅助生成唯一 ID,以避免哈希冲突。
class MyClass { public function my_method($content) { return $content . '<p>我是类方法!</p>'; } } $my_object = new MyClass(); add_filter('the_content', array($my_object, 'my_method')); // 在 _wp_filter_build_unique_id() 中: // $function_to_add 的值是 array(MyClass object, 'my_method') // 返回值可能是 '000000006a3a8e42000000001a1a1a1a' . 'my_method' (如果 spl_object_hash 存在) // 或者 '000000006a3a8e42000000001a1a1a1a' . 'my_method_1' (如果 spl_object_hash 不存在)
-
静态方法(Static Method)
elseif ( is_string( $function_to_add[0] ) ) { // Static Calling return $function_to_add[0] . '::' . $function_to_add[1]; }
如果数组的第一个元素是一个字符串,那说明你要调用的是一个静态方法。 直接将类名和方法名用
::
拼接起来,作为唯一 ID。class MyClass { public static function my_static_method($content) { return $content . '<p>我是静态方法!</p>'; } } add_filter('the_content', array('MyClass', 'my_static_method')); // 在 _wp_filter_build_unique_id() 中: // $function_to_add 的值是 array('MyClass', 'my_static_method') // 返回值是 'MyClass::my_static_method'
-
总结
咱们用一张表格来总结一下 _wp_filter_build_unique_id()
函数根据不同类型的 $function_to_add
生成唯一 ID 的策略:
$function_to_add 类型 |
生成 ID 的策略 | 示例 |
---|---|---|
字符串(String) | 直接返回字符串本身 | add_filter('the_content', 'my_custom_filter'); 返回 'my_custom_filter' |
对象(Object,闭包) | 将对象转换为数组 array(Closure object, '') ,然后使用 spl_object_hash() 获取对象的哈希值,并与空字符串拼接。如果 spl_object_hash() 不存在,则使用 $filter_id_count 辅助生成。 |
add_filter('the_title', function($title) { ... }); 返回 '000000006a3a8e42000000001a1a1a1a' 或 '000000006a3a8e42000000001a1a1a1a_1' |
数组(Array,对象方法) | 使用 spl_object_hash() 获取对象的哈希值,并与方法名拼接。如果 spl_object_hash() 不存在,则使用 $filter_id_count 辅助生成。 |
add_filter('the_content', array($my_object, 'my_method')); 返回 '000000006a3a8e42000000001a1a1a1a'.'my_method' 或 '000000006a3a8e42000000001a1a1a1a'.'my_method_1' |
数组(Array,静态方法) | 将类名和方法名用 :: 拼接。 |
add_filter('the_content', array('MyClass', 'my_static_method')); 返回 'MyClass::my_static_method' |
为什么需要唯一 ID?
你可能会问,为什么 WordPress 要费这么大力气生成唯一 ID 呢? 原因很简单:
- 防止重复添加: WordPress 需要确保同一个函数不会被多次添加到同一个钩子上。如果允许重复添加,那么这个函数就会被执行多次,可能会导致意想不到的问题。
- 方便移除: WordPress 提供了
remove_filter()
和remove_action()
函数,可以根据函数名或者唯一 ID 来移除已添加的钩子。 如果没有唯一 ID,就很难准确地移除指定的钩子,特别是对于匿名函数和闭包。
实际应用
了解了 _wp_filter_build_unique_id()
函数的原理,可以帮助我们更好地理解 WordPress 的钩子机制,并且在编写自定义插件或者主题时,避免一些常见的错误。
例如,如果你想移除一个匿名函数,你需要先保存这个匿名函数的引用,然后使用 remove_filter()
函数,传入钩子名称和匿名函数的引用。
$my_anonymous_function = function($content) {
return $content . '<p>我是偷偷加进去的!</p>';
};
add_filter('the_content', $my_anonymous_function);
// 移除这个匿名函数
remove_filter('the_content', $my_anonymous_function);
如果不保存匿名函数的引用,就无法移除它,因为它没有一个可识别的名称或者 ID。
总结的总结
_wp_filter_build_unique_id()
函数虽然看起来不起眼,但它在 WordPress 的钩子机制中扮演着重要的角色。它通过不同的策略,为不同类型的函数生成唯一的 ID,保证了钩子的正确性和可维护性。
希望今天的讲解能够帮助你更好地理解 WordPress 的底层机制,并且在实际开发中更加得心应手。
各位,下课!