問題描述

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

給出這個程式碼:

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