問題描述

假設的例子,但真實世界的適用性 (對某人學習,像我一樣) 。

給出這個代碼:

<?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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。