问题描述

假设的例子,但真实世界的适用性 (对某人学习,像我一样) 。

给出这个代码:

<?php

function send_money_to_grandma() {
     internetofThings("send grandma","$1");
}

add_action('init','send_money_to_grandma');
add_action('init','send_money_to_grandma');

好的,现在我提起我的 WP 网站并登录。我在 Admin 中浏览了几页。在我的笔记本电脑电池电量耗尽之前,动作’init’ 共触发 100 次。

第一个问题:我们送多少钱给奶奶?是 $ 1,$ 2,$ 100 或 $ 200(或其他?)

如果你也可以解释你的答案会令人敬畏的。

第二个问题:如果我们想确保我们只送奶奶 1 美元,那最好的办法是什么?全局变量 (信号量) 在第一次发送 $ 1 时设置’true’?或者还有其他一些测试,看看是否已经发生了一个动作,并阻止它多次发射?

第三个问题:这是插件开发人员担心的事情吗?我意识到我的例子是愚蠢的,但我正在考虑性能问题和其他意想不到的副作用 (例如,如果函数更新/插入数据库) 。

最佳解决方案

这里有一些随机的想法:

问题#1

How much money did we send to grandma?

对于 100 页的加载,我们发送了 100 x $ 1 = $ 100 。

这里我们实际上是指 100 x do_action( 'init' )调用。

没有关系,我们添加了两次:

add_action( 'init','send_money_to_grandma' );
add_action( 'init','send_money_to_grandma' );

因为回调和优先级 (默认值为 10) 是相同的。

我们可以检查 add_action 是如何构建全局 $wp_filter 数组的 add_filter 封装的:

function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
        global $wp_filter, $merged_filters;

        $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);
        $wp_filter[$tag][$priority][$idx] = array(
            'function'      => $function_to_add,
            'accepted_args' => $accepted_args
        );
        unset( $merged_filters[ $tag ] );
        return true;
}

如果我们改变了优先级:

add_action( 'init','send_money_to_grandma', 9 );
add_action( 'init','send_money_to_grandma', 10 );

那么我们将发送她的每页 2 x $ 1 加载或 100 页的加载 $ 200 。

如果回调不同,则相同:

add_action( 'init','send_money_to_grandma_1_dollar' );
add_action( 'init','send_money_to_grandma_also_1_dollar' );

问题#2

If we want to make sure we only send grandma $1

如果我们只想在每次加载的时候发送一次,那么应该这样做:

add_action( 'init','send_money_to_grandma' );

因为 init 钩子只被发射一次。我们可能会有其他挂钩,每页加载多次。

我们来电话:

add_action( 'someaction ','send_money_to_grandma' );

但是如果 someaction 每页加载 10 次,则会发生什么?

我们可以调整 send_money_to_grandma()功能

function send_money_to_grandma()
{
    if( ! did_action( 'someaction' ) )
        internetofThings("send grandma","$1");
}

或使用静态变量作为计数器:

function send_money_to_grandma()
{
    static $counter = 0;
    if( 0 === $counter++ )
        internetofThings("send grandma","$1");
}

如果我们只想运行一次 (永远!),那么我们可以通过 Options APIwp_options 表中注册一个选项:

function send_money_to_grandma()
{
    if( 'no' === get_option( 'sent_grandma_money', 'no' ) )
    {
        update_option( 'sent_grandma_money', 'yes' );
        internetofThings( "send grandma","$1" );
    }
}

如果我们每天要送她一次钱,那么我们可以使用 Transient API

function send_money_to_grandma()
{
    if ( false === get_transient( 'sent_grandma_money' ) ) )
    {
        internetofThings( "send grandma","$1" );
        set_transient( 'sent_grandma_money', 'yes', DAY_IN_SECONDS );
    }
}

甚至使用 wp-cron 。

请注意,您可能有 ajax 调用。以及。

有办法检查那些,例如与 DOING_AJAX

可能还有重定向,可能会中断流量。

那么我们可能只想限制后端,is_admin()或者不是:! is_admin()

问题 3

Is this something that plugin developers worry about?

是的,这很重要。

如果我们想让我们的奶奶很开心,我们会做:

add_action( 'all','send_money_to_grandma' );

但这对于表演… 和我们的钱包来说会非常糟糕;-)

次佳解决方案

这是对一个非常好的 Birgire’s answer 的评论比一个完整的答案,但不得不编写代码,评论不适合。

从答案可以看出,在 OP 示例代码中添加一次操作的唯一原因,即使 add_action()被调用两次,这是事实上使用相同的优先级。这不是真的。

add_filter 的代码中,重要的一个部分是_wp_filter_build_unique_id()函数调用,每个回调创建一个唯一的 id 。

如果你使用一个简单的变量,像一个保存一个函数名的字符串,例如"send_money_to_grandma",那么 id 将等于字符串本身,所以如果优先级相同,id 也是一样的,回调将被添加一次。

然而,事情并不总是那么简单。回调可能是 PHP 中的 callable

  • 函数名

  • 静态类方法

  • 动态类方法

  • 可调用对象

  • 关闭 (匿名函数)

前两个分别由一个字符串和一个 2 字符串数组 ('send_money_to_grandma'array('MoneySender', 'send_to_grandma')) 表示,所以 id 总是相同的,您可以确保如果优先级相同,则回调将被添加一次。

在所有其他 3 种情况中,id 取决于对象实例 (匿名函数是 PHP 中的一个对象),因此只有在对象是同一个实例时才添加一次回调,重要的是注意到同一个实例和同一个类是两个不同的东西。

举个例子:

class MoneySender {

   public function sent_to_grandma( $amount = 1 ) {
     // things happen here
   }

}

$sender1 = new MoneySender();
$sender2 = new MoneySender();

add_action( 'init', array( $sender1, 'sent_to_grandma' ) );
add_action( 'init', array( $sender1, 'sent_to_grandma' ) );
add_action( 'init', array( $sender2, 'sent_to_grandma' ) );

我们每页载入多少美元?

答案是 2,因为 id 为 WordPress 生成 $sender1$sender2 是不同的。

在这种情况下也是如此:

add_action( 'init', function() {
   sent_to_grandma();
} );

add_action( 'init', function() {
   sent_to_grandma();
} );

上面我使用闭包内的函数 sent_to_grandma,即使代码相同,2 个闭包是 Closure 对象的两个不同的实例,所以 WP 将创建 2 个不同的 id,即使优先级是相同。

第三种解决方案

您不能将相同的操作添加到相同的操作钩子,具有相同的优先级。

这样做是为了防止多个插件依赖于第三方插件的动作发生不止一次 (认为 woocommerce,所有它是第三方插件,如网关支付集成等) 。所以没有具体说明,奶奶依然很穷:

add_action('init','print_a_buck');
add_action('init','print_a_buck');

function print_a_buck() {
    echo '$1</br>';
}
add_action('wp', 'die_hard');
function die_hard() {
    die('hard');
}

但是,如果您添加这些操作的优先级:

add_action('init','print_a_buck', 1);
add_action('init','print_a_buck', 2);
add_action('init','print_a_buck', 3);

奶奶现在在口袋里丢了 4 美元 (1,2,3,默认值为 10) 。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。