Сколько раз будет выполняться этот код? (или, насколько богата бабушка?)
Гипотетический пример, но применимость в реальном мире (для кого-то, кто учится, как я).
Учитывая этот код:
<?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. Действие "инициализация" срабатывает в общей сложности 100 раз, прежде чем батарея моего ноутбука разрядится.
Первые вопросы: Сколько денег мы отправили бабушке? Это 1 доллар, 2 доллара, 100 долларов или 200 долларов (или что-то еще?)
Не могли бы вы также объяснить свой ответ это было бы потрясающе.
Вторые вопросы: Если мы хотим убедиться, что отправим бабушке только 1 доллар, как это лучше всего сделать? Глобальная переменная (семафор), которая получает значение "true" при первой отправке 1 доллара? Или есть какой-то другой тест, чтобы проверить, произошло ли уже действие, и предотвратить его многократное срабатывание?
Третий вопрос: Это то, о чем беспокоятся разработчики плагинов? Я понимаю, что мой пример глуп, но я думал об обеих проблемах с производительностью и другие неожиданные побочные эффекты (например, если функция обновляет/вставляет в базу данных).
3 answers
Вот несколько случайных мыслей по этому поводу:
Вопрос № 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
просто оболочка для 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 х 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
через API опций :
function send_money_to_grandma()
{
if( 'no' === get_option( 'sent_grandma_money', 'no' ) )
{
update_option( 'sent_grandma_money', 'yes' );
internetofThings( "send grandma","$1" );
}
}
Если мы хотим отправлять ей деньги один раз в день, тогда мы сможем использовать Временный 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' );
Но это было бы очень плохо для производительности... и наш кошелек;-)
Это скорее комментарий к очень хорошему ответу Биргира, чем полный ответ, но для написания кода комментарии не подходят.
Из ответа может показаться, что единственная причина, по которой действие добавляется один раз в примере кода OP, даже если add_action()
вызывается дважды, заключается в том, что используется один и тот же приоритет. Это неправда.
В коде add_filter
важной частью является _wp_filter_build_unique_id()
вызов функции, который создает уникальный идентификатор для обратный вызов.
Если вы используете простую переменную, например строку, содержащую имя функции, например"send_money_to_grandma"
, то идентификатор будет равен самой строке, поэтому, если приоритет тот же, то и идентификатор тот же, обратный вызов добавляется один раз.
Однако не всегда все так просто. Обратными вызовами может быть все, что есть callable
в PHP:
- имена функций
- статические методы класса
- методы динамического класса
- вызываемый объекты
- замыкания (анонимные функции)
Первые два представлены соответственно строкой и массивом из 2 строк ('send_money_to_grandma'
и array('MoneySender', 'send_to_grandma')
), поэтому идентификатор всегда один и тот же, и вы можете быть уверены, что обратный вызов добавляется один раз, если приоритет одинаков.
Во всех остальных 3 случаях идентификатор зависит от экземпляров объектов (анонимная функция является объектом в 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 генерирует для $sender1
и $sender2
, отличается.
То же самое происходит и в этом случае:
add_action( 'init', function() {
sent_to_grandma();
} );
add_action( 'init', function() {
sent_to_grandma();
} );
Выше я использовал функцию sent_to_grandma
внутри замыканий, и даже если код идентичен, 2 замыкания являются 2 разными экземплярами объекта \Closure
, поэтому WP создаст 2 разных идентификатора, что приведет к тому, что действие будет добавлено дважды, даже если приоритет один и тот же.
Вы не можете добавить то же действие в тот же крючок действия с тем же приоритетом.
Это сделано для того, чтобы несколько плагинов не зависели от действий сторонних плагинов более одного раза (подумайте о 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).