分析 WordPress `remove_action()` 和 `remove_filter()` 函数源码:从钩子数组中删除函数的实现原理。

各位观众老爷,大家好!我是今天的讲师,咱们今天聊聊WordPress里两个特别重要的函数:remove_action()remove_filter()。它们俩就像是WordPress这座大厦里的拆迁队,专门负责把挂在各种钩子上的函数给“请”下来。

别看名字不一样,其实它们背后的原理几乎是一样的,都是在折腾WordPress内部的钩子数组。所以,咱们就以 remove_action() 为主,讲明白了它,remove_filter() 自然也就懂了。

一、钩子是个啥?

在开始拆迁之前,咱们得先搞明白“钩子”是个啥。想象一下,WordPress的运行就像一条流水线,每个环节都可能需要我们插手做点啥。钩子就像是这条流水线上预留的接口,我们可以在这些接口上挂上自己的函数,让它们在特定的时间点执行。

WordPress里有两种钩子:

  • Action (动作钩子): 在某个事件发生时执行函数。比如,文章发布后、主题初始化时等等。
  • Filter (过滤器钩子): 用于修改数据。比如,文章内容、标题、摘要等等。

二、remove_action() 的庐山真面目

remove_action() 函数的原型是这样的:

remove_action( string $tag, callable $function_to_remove, int $priority = 10 );

参数解释:

  • $tag: 钩子的名称,也就是你要拆哪个钩子上的函数。
  • $function_to_remove: 要移除的函数名(或者类方法)。
  • $priority: 函数的优先级,也就是它挂在钩子上的顺序。默认是10。

三、remove_action() 的内部逻辑

要理解 remove_action() 的工作原理,我们需要深入到 WordPress 的核心类 WP_Hook 中,这个类负责管理所有的钩子。remove_action() 最终会调用 WP_Hook 类中的 remove_filter 方法来完成移除操作。

让我们简化一下 WP_Hook 类中相关部分的代码,方便理解:

class WP_Hook {

    /**
     * 存储已注册的函数.
     *
     * @var array
     */
    public $callbacks = array();

    /**
     * 判断钩子是否已经执行过.
     *
     * @var bool
     */
    protected $doing_action = false;

    /**
     * 添加一个函数到钩子.
     *
     * @param int      $priority   The priority at which the function should be executed.
     * @param callable $function   The function name to be executed.
     * @param int      $accepted_args The number of arguments the function accepts.
     */
    public function add_filter( $priority, $function, $accepted_args ) {
        // ...省略添加函数的逻辑,这里只关注移除

    }

    /**
     * 移除一个函数从钩子.
     *
     * @param int      $priority   The priority at which the function should be executed.
     * @param callable $function   The function name to be executed.
     * @return bool Whether the function existed before unregistering.
     */
    public function remove_filter( $priority, $function ) {
        if ( ! isset( $this->callbacks[ $priority ] ) ) {
            return false;
        }

        $function = $this->get_callable( $function );

        $removed = false;

        foreach ( $this->callbacks[ $priority ] as $identifier => $callback ) {
            if ( $callback['function'] === $function ) {
                unset( $this->callbacks[ $priority ][ $identifier ] );
                $removed = true;
            }
        }

        if ( $removed ) {
            unset( $this->merged_filters );
        }

        return $removed;
    }

    /**
     * 根据传入的函数信息,返回可调用的形式.
     *
     * @param callable $function Function to canonicalize.
     * @return string|callable  The canonicalized function.
     */
    protected function get_callable( $function ) {
        if ( is_string( $function ) ) {
            return trim( $function );
        }

        if ( is_object( $function ) ) {
            $function = array( $function, '__invoke' );
        }

        if ( is_array( $function ) && isset( $function[0], $function[1] ) && is_object( $function[0] ) ) {
            return array( $function[0], $function[1] );
        }

        return $function;
    }
}

下面我们一步步分析 remove_filter() 的代码:

  1. 检查优先级是否存在:

    if ( ! isset( $this->callbacks[ $priority ] ) ) {
        return false;
    }

    首先,函数会检查指定的优先级 $priority 是否存在于 $this->callbacks 数组中。如果不存在,说明这个优先级下没有注册任何函数,直接返回 false,表示移除失败。

  2. 规范化函数名:

    $function = $this->get_callable( $function );

    这一步非常重要。因为我们要移除的函数可能以多种形式传入,比如字符串(函数名)、数组(类方法)、闭包等等。get_callable() 函数负责将这些不同形式的函数统一规范化,以便后续的比较。

    get_callable() 函数会处理以下情况:

    • 字符串: 直接返回,并去除首尾空格。
    • 对象: 转换为 array( $object, '__invoke' ),其中 __invoke 是魔术方法,用于将对象当作函数调用。
    • 数组: 如果数组的第一个元素是对象,则假定是类方法,返回 array( $object, $method )
    • 其他: 直接返回。
  3. 遍历并移除函数:

    $removed = false;
    foreach ( $this->callbacks[ $priority ] as $identifier => $callback ) {
        if ( $callback['function'] === $function ) {
            unset( $this->callbacks[ $priority ][ $identifier ] );
            $removed = true;
        }
    }

    这是核心部分。代码遍历指定优先级下的所有已注册函数,逐个比较它们的函数名($callback['function'])和我们要移除的函数名 $function

    • 如果找到了匹配的函数,就使用 unset() 将其从 $this->callbacks 数组中删除,并将 $removed 标记设置为 true
    • $identifier$this->callbacks[ $priority ] 数组的键名,用于唯一标识每个函数。
  4. 清理缓存:

    if ( $removed ) {
        unset( $this->merged_filters );
    }

    如果成功移除了函数,就清除 $this->merged_filters 缓存。这个缓存用于优化钩子的执行效率,但在函数被移除后,需要清除缓存以保证下次执行时不会包含已移除的函数。

  5. 返回结果:

    return $removed;

    最后,函数返回 $removed 变量的值,表示是否成功移除了函数。

四、remove_filter() 的代码实现

remove_filter()remove_action() 的实现几乎一模一样,只是名字不同而已。

remove_filter( string $tag, callable $function_to_remove, int $priority = 10 );

五、举几个栗子

光说不练假把式,咱们来几个实际的例子:

  1. 移除 WordPress 默认的 wpautop 函数:

    wpautop 函数会自动给文章内容添加 <p> 标签,有时候我们不需要这个功能,就可以这样移除它:

    remove_filter( 'the_content', 'wpautop' );

    这个例子很简单,直接移除了 the_content 钩子上的 wpautop 函数,优先级默认是10。

  2. 移除类方法:

    假设我们有一个类 MyClass,它有一个方法 my_filter 挂在了 the_content 钩子上:

    class MyClass {
        public function my_filter( $content ) {
            return $content . ' - Edited by MyClass';
        }
    }
    
    $my_class = new MyClass();
    add_filter( 'the_content', array( $my_class, 'my_filter' ) );

    要移除这个类方法,我们需要这样做:

    remove_filter( 'the_content', array( $my_class, 'my_filter' ) );

    注意,这里要移除的是一个数组,包含类实例和方法名。

  3. 移除指定优先级的函数:

    有时候,同一个钩子上可能会挂多个同名的函数,但优先级不同。 比如:

    add_filter( 'the_title', 'my_title_filter', 5 );
    add_filter( 'the_title', 'my_title_filter', 15 );

    要移除优先级为5的 my_title_filter 函数,可以这样做:

    remove_filter( 'the_title', 'my_title_filter', 5 );

六、注意事项

  • 确保在正确的时机移除: remove_action()remove_filter() 必须在要移除的函数被添加之后调用。通常,我们会在 after_setup_themeinit 动作钩子上调用它们。
  • 精确匹配函数名和优先级: 要成功移除函数,必须提供正确的函数名和优先级。如果函数名或优先级不匹配,remove_action()remove_filter() 不会起作用。
  • 了解钩子的执行顺序: 钩子上的函数是按照优先级从小到大执行的。 移除函数可能会影响钩子的执行结果。

七、总结

remove_action()remove_filter() 是 WordPress 中非常强大的工具,可以用来修改主题和插件的行为。 理解它们的原理和用法,可以让我们更灵活地控制 WordPress 的运行。 掌握这两个函数,就像掌握了拆迁队,可以随意地对WordPress这座大厦进行改造,让它更符合我们的需求。

希望今天的讲座对大家有所帮助! 感谢各位的观看!

发表回复

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