分析 WordPress `_wp_filter_build_unique_id()` 函数源码:匿名函数和闭包的唯一 ID 生成。

各位靓仔靓女,早上好!今天咱们来扒一扒 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。

代码分解

  1. 静态变量 $filter_id_count

    static $filter_id_count = 0;

    这个静态变量用于在 spl_object_hash 不可用时生成唯一 ID,主要目的是避免不同匿名函数返回相同的 hash 值。

  2. 判断 $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')
  3. 处理对象方法和静态方法

    接下来,函数会判断数组的第一个元素($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 的底层机制,并且在实际开发中更加得心应手。

各位,下课!

发表回复

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