探究 WordPress `_wp_filter_build_unique_id()` 函数源码:钩子唯一 ID 的生成逻辑。

各位老铁,大家好!我是今天的主讲人,咱们今天来聊聊 WordPress 源码里一个挺有意思的小家伙:_wp_filter_build_unique_id()。 别看它名字长,其实干的活儿挺简单,就是给 WordPress 钩子(Hook)生成一个独一无二的 ID。

为啥要给钩子生成唯一 ID 呢? 这就涉及到 WordPress 插件和主题机制的核心了。你想啊,成百上千的插件和主题,都想往同一个钩子上挂载自己的函数,如果没有一个唯一 ID 来区分,那不就乱套了吗?

所以,这个函数的作用,就是确保每个挂载到钩子上的函数都有一个独一无二的身份证,方便 WordPress 内部进行管理和调用。

咱们废话不多说,直接撸代码!

/**
 * Generates a unique function ID.
 *
 * @access private
 *
 * @param string   $tag             The name of the filter to hook the $function_to_add to.
 * @param callable $function_to_add Function to be called when the filter is applied.
 * @param int      $priority        Used to specify the order in which the functions associated with a
 *                                  particular action are executed. Lower numbers correspond with earlier
 *                                  execution, and functions with the same priority are executed in the
 *                                  order in which they were added to the action.
 *
 * @return string A unique function ID.
 */
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 {
            $obj_idx = get_class( $function_to_add[0] ) . $function_to_add[1];
            if ( ! isset( $function_to_add[0]->wp_filter_id ) ) {
                $filter_id_count ++;
                $function_to_add[0]->wp_filter_id = $filter_id_count;
            }
            return $obj_idx . $function_to_add[0]->wp_filter_id;
        }
    } elseif ( is_string( $function_to_add[0] ) ) {
        // Static Calling.
        return $function_to_add[0] . '::' . $function_to_add[1];
    }
}

咱们一行一行地拆解:

1. 函数声明和文档注释:

/**
 * Generates a unique function ID.
 *
 * @access private
 *
 * @param string   $tag             The name of the filter to hook the $function_to_add to.
 * @param callable $function_to_add Function to be called when the filter is applied.
 * @param int      $priority        Used to specify the order in which the functions associated with a
 *                                  particular action are executed. Lower numbers correspond with earlier
 *                                  execution, and functions with the same priority are executed in the
 *                                  order in which they were added to the action.
 *
 * @return string A unique function ID.
 */
function _wp_filter_build_unique_id( $tag, $function_to_add, $priority ) {
  • @access private: 说明这个函数是 WordPress 内部使用的,不建议开发者直接调用。
  • $tag: 钩子的名称,比如 ‘wp_head’ 或者 ‘the_content’。
  • $function_to_add: 要挂载到钩子上的函数。它可以是函数名字符串、数组(用于类方法或静态方法)或者匿名函数(闭包)。
  • $priority: 优先级,数值越小,优先级越高。
  • @return string: 返回生成的唯一 ID。

2. 静态变量:

static $filter_id_count = 0;
  • static $filter_id_count = 0;: 这是一个静态变量,用于记录已经生成的 ID 数量。 静态变量的特点是,它的值在函数多次调用之间会被保留。 这个变量主要用于处理一些特殊情况,咱们后面会讲到。

3. 处理函数名是字符串的情况:

if ( is_string( $function_to_add ) ) {
    return $function_to_add;
}
  • is_string( $function_to_add ): 如果 $function_to_add 是一个字符串,说明直接传递的是函数名。
  • return $function_to_add;: 直接返回函数名作为唯一 ID。 这种情况下,WordPress 认为函数名本身就足够唯一了。

举个例子:

add_filter( 'the_content', 'my_content_filter' ); // 函数名是字符串

在这种情况下,_wp_filter_build_unique_id() 会直接返回 'my_content_filter'

4. 处理匿名函数 (闭包) 的情况:

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;
}
  • is_object( $function_to_add ): 如果 $function_to_add 是一个对象,通常意味着它是一个匿名函数 (闭包)。 在 PHP 中,闭包被实现为对象。
  • $function_to_add = array( $function_to_add, '' );: 将闭包对象包装成一个数组, 数组的第一个元素是闭包对象,第二个元素是一个空字符串。 这是为了统一处理类方法和静态方法的情况。
  • else { $function_to_add = (array) $function_to_add; }: 如果不是对象,则强制转换为数组,用于处理类方法和静态方法。

举个例子:

add_filter( 'the_content', function( $content ) { // 匿名函数
    return $content . ' Added by anonymous function';
} );

在这种情况下,$function_to_add 是一个 Closure 对象,会被转换成 array( $closure_object, '' )

5. 处理类方法的情况:

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 {
        $obj_idx = get_class( $function_to_add[0] ) . $function_to_add[1];
        if ( ! isset( $function_to_add[0]->wp_filter_id ) ) {
            $filter_id_count ++;
            $function_to_add[0]->wp_filter_id = $filter_id_count;
        }
        return $obj_idx . $function_to_add[0]->wp_filter_id;
    }
}
  • is_object( $function_to_add[0] ): 如果 $function_to_add[0] 是一个对象,说明调用的是一个类的方法。
  • function_exists( 'spl_object_hash' ): 检查 spl_object_hash() 函数是否存在。 这个函数是 PHP SPL 扩展提供的,用于生成对象的唯一哈希值。
  • return spl_object_hash( $function_to_add[0] ) . $function_to_add[1];: 如果 spl_object_hash() 存在,就使用它生成对象的唯一哈希值,并和方法名 $function_to_add[1] 拼接起来作为唯一 ID。 spl_object_hash 能保证在脚本运行期间,同一个对象的哈希值是不变的。
  • else { ... }: 如果 spl_object_hash() 不存在(比如 PHP 版本太老),就使用一种备用的方法。
    • $obj_idx = get_class( $function_to_add[0] ) . $function_to_add[1];: 获取类名和方法名,拼接起来。
    • if ( ! isset( $function_to_add[0]->wp_filter_id ) ) { ... }: 检查对象是否已经有一个 wp_filter_id 属性。 这个属性是用来保存一个自增的 ID 的,目的是为了在没有 spl_object_hash() 的情况下,尽可能保证唯一性。
      • $filter_id_count ++;: 自增静态变量 $filter_id_count
      • $function_to_add[0]->wp_filter_id = $filter_id_count;: 将自增的 ID 赋值给对象的 wp_filter_id 属性。
    • return $obj_idx . $function_to_add[0]->wp_filter_id;: 将类名、方法名和自增的 ID 拼接起来作为唯一 ID。

举个例子:

class MyClass {
    public function my_method( $content ) {
        return $content . ' Added by class method';
    }
}

$my_object = new MyClass();
add_filter( 'the_content', array( $my_object, 'my_method' ) ); // 类方法

在这种情况下,$function_to_addarray( $my_object, 'my_method' )。 如果 spl_object_hash() 存在,则返回类似 4e0fb1a60e02a::my_method 的 ID。 如果 spl_object_hash() 不存在,则返回类似 MyClassmy_method1 的 ID (假设这是第一次给这个类的对象分配 ID)。

6. 处理静态方法的情况:

elseif ( is_string( $function_to_add[0] ) ) {
    // Static Calling.
    return $function_to_add[0] . '::' . $function_to_add[1];
}
  • is_string( $function_to_add[0] ): 如果 $function_to_add[0] 是一个字符串,说明调用的是一个静态方法。
  • return $function_to_add[0] . '::' . $function_to_add[1];: 将类名和方法名用 :: 拼接起来作为唯一 ID。

举个例子:

class MyClass {
    public static function my_static_method( $content ) {
        return $content . ' Added by static method';
    }
}

add_filter( 'the_content', array( 'MyClass', 'my_static_method' ) ); // 静态方法

在这种情况下,$function_to_addarray( 'MyClass', 'my_static_method' ), 返回的唯一 ID 是 MyClass::my_static_method

总结一下, _wp_filter_build_unique_id() 函数的生成逻辑可以概括为以下表格:

$function_to_add 类型 生成 ID 的方式 例子
字符串 (函数名) 直接返回函数名。 add_filter( 'the_content', 'my_content_filter' ); 返回 'my_content_filter'
闭包 (匿名函数) 将闭包对象包装成数组 array( $closure_object, '' ), 然后按照类方法的方式处理 (使用 spl_object_hash 或者自增 ID)。 add_filter( 'the_content', function( $content ) { ... } ); 返回类似 4e0fb1a60e02a 的哈希值
类方法 如果 spl_object_hash() 存在,使用它生成对象的唯一哈希值,并和方法名拼接起来。 如果 spl_object_hash() 不存在,则使用类名、方法名和自增 ID 拼接起来。 add_filter( 'the_content', array( $my_object, 'my_method' ) ); 返回类似 MyClassmy_method1
静态方法 将类名和方法名用 :: 拼接起来。 add_filter( 'the_content', array( 'MyClass', 'my_static_method' ) ); 返回 'MyClass::my_static_method'

一些需要注意的点:

  • spl_object_hash() 的重要性: spl_object_hash() 函数是生成对象唯一 ID 的首选方法,因为它能保证在脚本运行期间,同一个对象的哈希值是不变的。 如果 PHP 版本支持,一定要使用它。
  • 自增 ID 的局限性: 在没有 spl_object_hash() 的情况下,使用自增 ID 只能在一定程度上保证唯一性。 如果同一个类的多个对象都挂载到同一个钩子上,并且在同一个请求中,那么它们的自增 ID 可能会重复。 虽然这种情况比较少见,但还是需要注意。
  • 钩子的优先级: _wp_filter_build_unique_id() 函数的 $priority 参数并没有被使用到。 钩子的优先级是在另一个地方进行管理的,这个函数只负责生成唯一 ID。

为什么要区分类方法和静态方法?

主要原因是为了保证 ID 的唯一性。 静态方法属于类本身,而类方法属于类的实例对象。 如果不区分,可能会导致不同的实例对象的方法,生成相同的 ID,从而引起冲突。

实际应用场景:

_wp_filter_build_unique_id() 函数在 WordPress 内部被广泛使用, 主要是在 add_filter()remove_filter() 函数中。

  • add_filter(): 在添加钩子时,使用 _wp_filter_build_unique_id() 生成唯一 ID,并将函数和 ID 存储在全局的 $wp_filter 数组中。
  • remove_filter(): 在移除钩子时,使用 _wp_filter_build_unique_id() 生成要移除的函数的唯一 ID,然后在 $wp_filter 数组中找到对应的函数并移除。

总结:

_wp_filter_build_unique_id() 函数虽然看起来很简单,但它在 WordPress 钩子机制中扮演着重要的角色。 它通过不同的策略,为不同的函数类型生成唯一 ID,保证了插件和主题之间的兼容性和稳定性。

希望今天的讲解能帮助大家更好地理解 WordPress 的钩子机制, 也能对大家在开发 WordPress 插件和主题时有所启发。

今天的讲座就到这里,谢谢大家! 如果有什么问题,欢迎留言讨论。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注