问题描述
假设的例子,但真实世界的适用性 (对某人学习,像我一样) 。
给出这个代码:
<?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 API 在 wp_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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。