問題描述
假設的例子,但真實世界的適用性 (對某人學習,像我一樣) 。
給出這個代碼:
<?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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。