此代码将运行多少次?(或者,奶奶有多富?)


20

假设的示例,但在现实世界中适用(对于像我这样学习的人)。

给出以下代码:

<?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”吗?还是有其他测试可以查看某个动作是否已经发生并阻止其多次触发?

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


2
必须承认,这是很长时间以来最好的问题之一;-)
Pieter Goosen

Answers:


21

这是一些随机的想法:

问题1

我们寄给奶奶多少钱?

对于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仅将wrapper用于add_filter构造全局$wp_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(每页加载)或$ 200(每100页加载)。

如果回调不同,则相同:

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

问题2

如果我们要确保只寄给奶奶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");
}

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

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

这是插件开发人员担心的事情吗?

是的,这很重要。

如果我们想让我们的祖母非常高兴,我们将:

add_action( 'all','send_money_to_grandma' );

但这对于性能和我们的钱包是非常不利的;-)


哇-谢谢你这么详尽的回应;这极大地帮助了!
CC

1
不用客气-我真的很喜欢您如何表达您的问题;-) @CC
birgire

2
归根结底,我们要让钱包和奶奶开心,所以这一切都是为了找到完美的和谐/平衡;-)
Pieter Goosen

+1是一个非常不错的答案,但是值得一说的是,添加操作的时间还取决于回调id,并且在处理对象时,事情要复杂一些。在这里很难更好地解释概念,我ll写一个答案...
gmazzap

谢谢@gmazzap-是的,那太好了,因为我没有介绍第三个键$ idx和_wp_filter_build_unique_id(),只显示了它;-)
birgire

8

比起完整的答案,这更像是对Birgire很好的答案的评论,但是必须编写代码,注释不适合。

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

在函数add_filter的重要部分中,_wp_filter_build_unique_id()函数调用会在每个callback中创建一个唯一的ID 。

如果您使用一个简单的变量,例如带有函数名称的字符串,例如"send_money_to_grandma"id,则id将等于字符串本身,因此,如果优先级相同,且id相同,则回调将被添加一次。

但是,事情并非总是那么简单。回调可以是callablePHP中的任何东西:

  • 功能名称
  • 静态类方法
  • 动态类方法
  • 可调用对象
  • 闭包(匿名函数)

前两个分别由一个字符串和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,因为WordPress生成的ID $sender1$sender2不同。

在这种情况下也会发生相同的情况:

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

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

上面我sent_to_grandma在闭包内部使用了函数,即使代码相同,两个闭包也是\Closure对象的2个不同实例,因此WP将创建2个不同的id,即使优先级相同,该操作也会被添加两次。


4

你可以不加同样的动作同一动作钩,用相同的优先级

这样做是为了防止多个插件依赖第三方插件的动作多次发生(想想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)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.