解析 WordPress `doing_action()` 和 `doing_filter()` 函数源码:如何判断当前代码是否在执行特定的钩子。

各位技术爱好者,大家好!今天咱们来聊聊 WordPress 源码里两个挺有意思的函数:doing_action()doing_filter()。这俩哥们儿,可以说是 WordPress 钩子机制里的“侦察兵”,专门负责摸清底细,看看当前代码是不是正儿八经地在某个 Action 或者 Filter 钩子的“地盘”里执行。

一、钩子机制:WordPress 的“神经系统”

在深入 doing_action()doing_filter() 之前,咱们先简单回顾一下 WordPress 的钩子机制。简单来说,它就像 WordPress 这具庞大身躯里的“神经系统”,允许开发者在不修改核心代码的情况下,插入自己的代码,扩展或修改 WordPress 的功能。

钩子分为两种主要类型:

  • Action (动作钩子): 允许你在特定事件发生时执行自定义代码。比如,文章发布后、主题加载前等等。
  • Filter (过滤器钩子): 允许你修改 WordPress 的数据。比如,文章内容、标题等等。

有了钩子,WordPress 才能如此灵活和可扩展,各种插件和主题才能百花齐放。

二、doing_action():Action 钩子的“探测器”

doing_action() 函数的作用是:判断当前代码是否正在执行某个特定的 Action 钩子。如果正在执行,它会返回 true;否则,返回 false。 如果没有传入参数,则判断是否在执行任何 Action 钩子。

咱们来看看 doing_action() 的源码 (基于 WordPress 最新版):

/**
 * Check if any action is currently being run.
 *
 * @since 2.5.0
 *
 * @param string|null $action Optional. The name of the action to check for.
 * @return bool True if the action is currently being run, false otherwise.
 */
function doing_action( $action = null ) {
    global $wp_current_filter;

    if ( null === $action ) {
        return ! empty( $wp_current_filter );
    }

    return in_array( $action, $wp_current_filter, true );
}

这段代码的核心逻辑其实很简单:

  1. 全局变量 $wp_current_filter 这是 WordPress 维护的一个全局数组,用来记录当前正在执行的 Action 和 Filter 钩子的名称。 每次执行 Action 或 Filter 时,钩子的名称都会被添加到这个数组里;执行完毕后,又会被移除。 所以,$wp_current_filter 就像一个“执行栈”,记录着当前代码的“调用链”。

  2. 判断 $action 是否为空:

    • 如果 $action 为空 (null),那么 doing_action() 就简单地判断 $wp_current_filter 是否为空。如果 $wp_current_filter 不为空,说明当前正在执行某个 Action 或 Filter,返回 true;否则,返回 false

    • 如果 $action 不为空,那么 doing_action() 就会在 $wp_current_filter 数组里查找是否存在 $action 这个元素。如果存在,说明当前正在执行名为 $action 的 Action 钩子,返回 true;否则,返回 falsein_array函数的第三个参数为 true,代表严格类型检查。

举个例子:

假设我们有如下代码:

add_action( 'wp_footer', 'my_footer_function' );

function my_footer_function() {
    if ( doing_action( 'wp_footer' ) ) {
        echo '<p>正在执行 wp_footer 钩子!</p>';
    } else {
        echo '<p>没有执行 wp_footer 钩子!</p>';
    }

    if ( doing_action() ) {
        echo '<p>正在执行某个 Action 钩子!</p>';
    } else {
        echo '<p>没有执行任何 Action 钩子!</p>';
    }
}

这段代码的意思是:当 wp_footer 钩子被触发时,执行 my_footer_function 函数。在这个函数里,我们用 doing_action('wp_footer') 来判断当前是否正在执行 wp_footer 钩子,并用 doing_action() 来判断是否正在执行任何Action钩子。

显然,在 my_footer_function 函数内部,doing_action('wp_footer')doing_action() 都会返回 true,所以会输出:

<p>正在执行 wp_footer 钩子!</p>
<p>正在执行某个 Action 钩子!</p>

三、doing_filter():Filter 钩子的“探测器”

doing_filter() 函数的作用与 doing_action() 类似,只不过它是用来判断当前代码是否正在执行某个特定的 Filter 钩子。 如果正在执行,它会返回 true;否则,返回 false。如果没有传入参数,则判断是否在执行任何Filter钩子。

咱们来看看 doing_filter() 的源码 (基于 WordPress 最新版):

/**
 * Check if any filter is currently being run.
 *
 * @since 3.9.0
 *
 * @param string|null $filter Optional. The name of the filter to check for.
 * @return bool True if the filter is currently being run, false otherwise.
 */
function doing_filter( $filter = null ) {
    global $wp_current_filter;

    if ( null === $filter ) {
        return ! empty( $wp_current_filter );
    }

    return in_array( $filter, $wp_current_filter, true );
}

是不是觉得和 doing_action() 的源码几乎一模一样? 没错,它们的核心逻辑完全相同,都是通过检查全局变量 $wp_current_filter 来判断当前是否正在执行指定的钩子。

举个例子:

假设我们有如下代码:

add_filter( 'the_content', 'my_content_filter' );

function my_content_filter( $content ) {
    if ( doing_filter( 'the_content' ) ) {
        $content .= '<p>正在通过 the_content 过滤器!</p>';
    } else {
        $content .= '<p>没有通过 the_content 过滤器!</p>';
    }

    if ( doing_filter() ) {
        $content .= '<p>正在通过某个 Filter 过滤器!</p>';
    } else {
        $content .= '<p>没有通过任何 Filter 过滤器!</p>';
    }

    return $content;
}

这段代码的意思是:当 the_content 过滤器被应用时,执行 my_content_filter 函数。在这个函数里,我们用 doing_filter('the_content') 来判断当前是否正在执行 the_content 过滤器,并用 doing_filter() 来判断是否正在执行任何Filter钩子。

显然,在 my_content_filter 函数内部,doing_filter('the_content')doing_filter() 都会返回 true,所以文章内容会追加:

<p>正在通过 the_content 过滤器!</p>
<p>正在通过某个 Filter 过滤器!</p>

四、doing_action()doing_filter() 的应用场景

那么,doing_action()doing_filter() 到底有什么用呢? 它们主要用于以下几种场景:

  1. 避免无限循环: 在某些情况下,如果在 Action 或 Filter 钩子的处理函数里又触发了同一个钩子,可能会导致无限循环。doing_action()doing_filter() 可以用来检测这种情况,避免程序崩溃。

    add_action( 'save_post', 'my_save_post_function' );
    
    function my_save_post_function( $post_id ) {
        // 防止无限循环
        if ( doing_action( 'save_post' ) ) {
            return;
        }
    
        // ... 其他处理逻辑 ...
    }
  2. 条件执行代码: 有时候,我们希望某些代码只在特定的 Action 或 Filter 钩子被触发时才执行。doing_action()doing_filter() 可以用来判断当前是否满足条件。

    add_action( 'wp_footer', 'my_footer_function' );
    
    function my_footer_function() {
        // 只在 wp_footer 钩子触发时才执行
        if ( doing_action( 'wp_footer' ) ) {
            echo '<p>版权信息</p>';
        }
    }
  3. 调试和排错: 在调试 WordPress 插件或主题时,doing_action()doing_filter() 可以帮助我们确认代码是否按照预期在特定的钩子里执行。

    add_action( 'wp_footer', 'my_footer_function' );
    
    function my_footer_function() {
        // 调试信息
        if ( doing_action( 'wp_footer' ) ) {
            error_log( 'my_footer_function 正在执行' );
        }
    }
  4. 处理嵌套钩子的情况: 有时候,一个钩子的处理函数内部会触发另一个钩子。doing_action()doing_filter() 可以用来区分当前处于哪个钩子的上下文中。

    add_action('init', 'outer_action');
    add_action('inner_action', 'inner_action_callback');
    
    function outer_action() {
       echo "Outer Action started. Doing action: " . (doing_action('init') ? 'init' : 'none') . "<br>";
       do_action('inner_action');
       echo "Outer Action finished. Doing action: " . (doing_action('init') ? 'init' : 'none') . "<br>";
    }
    
    function inner_action_callback() {
       echo "Inner Action callback started. Doing action: " . (doing_action('inner_action') ? 'inner_action' : 'none') . " and init: " . (doing_action('init') ? 'init' : 'none') . "<br>";
       echo "Inner Action callback finished. Doing action: " . (doing_action('inner_action') ? 'inner_action' : 'none') . " and init: " . (doing_action('init') ? 'init' : 'none') . "<br>";
    }
    
    // Output:
    // Outer Action started. Doing action: init
    // Inner Action callback started. Doing action: inner_action and init: init
    // Inner Action callback finished. Doing action: inner_action and init: init
    // Outer Action finished. Doing action: init
    

    这个例子清晰地展示了嵌套钩子执行时,doing_action() 如何帮助我们跟踪当前执行的上下文。 在inner_action_callback中,我们可以同时检测到inner_actioninit正在被执行。

五、注意事项

  • doing_action()doing_filter() 只能判断当前代码是否 正在 执行指定的钩子。 如果钩子已经执行完毕,或者尚未开始执行,它们都会返回 false
  • 由于 $wp_current_filter 是一个全局变量,因此在并发环境下(例如,使用多线程或异步任务),可能会出现竞争条件,导致 doing_action()doing_filter() 的结果不准确。 在这种情况下,需要采取适当的同步措施来保证数据的完整性。虽然 WordPress 本身很少涉及多线程,但在一些高级应用场景下,需要注意这个问题。
  • 避免过度使用 doing_action()doing_filter()。 过度依赖这些函数可能会导致代码逻辑过于复杂,可读性降低。 应该尽量使用更明确的方式来控制代码的执行流程,例如,通过传递参数、使用类方法等等。

六、总结

doing_action()doing_filter() 是 WordPress 钩子机制中非常有用的工具,可以帮助我们判断当前代码是否正在执行特定的 Action 或 Filter 钩子。 掌握了它们的用法,可以让我们更好地理解 WordPress 的运行机制,编写更健壮、更灵活的插件和主题。

下面用一个表格总结一下这两个函数:

函数名称 作用 参数 返回值
doing_action() 判断当前代码是否正在执行某个 Action 钩子。 可选,Action 钩子的名称 (字符串)。如果为空,则判断是否正在执行任何 Action 钩子。 如果正在执行指定的 Action 钩子或任何 Action 钩子,则返回 true;否则,返回 false
doing_filter() 判断当前代码是否正在执行某个 Filter 钩子。 可选,Filter 钩子的名称 (字符串)。如果为空,则判断是否正在执行任何 Filter 钩子。 如果正在执行指定的 Filter 钩子或任何 Filter 钩子,则返回 true;否则,返回 false

希望今天的讲解能帮助大家更好地理解 doing_action()doing_filter() 这两个函数。 如果大家还有什么疑问,欢迎随时提问。 感谢大家的聆听!

发表回复

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