Сколько раз будет выполняться этот код? (или насколько богата бабушка?)

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 доллар, как лучше это сделать? Глобальная переменная (семафор), которая получает значение 'true' при первой отправке $ 1? Или есть какой-то другой тест, чтобы увидеть, произошло ли какое-либо действие уже и предотвратить его многократное срабатывание?

Третий вопрос: это то, что беспокоит разработчиков плагинов? Я понимаю, что мой пример глуп, но я думал как о проблемах производительности, так и о других неожиданных побочных эффектах (например, если функция обновляется / вставляется в базу данных).

CC
источник
2
Надо признать, это один из лучших вопросов за долгое время ;-)
Питер Гусен

Ответы:

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это просто оболочка для 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" );
    }
}

Если мы хотим отправлять ей деньги раз в день, тогда мы можем использовать 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' );

но это было бы очень плохо для производительности ... и наш кошелек ;-)

birgire
источник
вау - спасибо за такой обстоятельный ответ; это очень помогает!
CC
1
Не за что - мне очень понравилось, как вы сформулировали свой вопрос ;-) @CC
birgire
2
В конце концов, мы хотим, чтобы наши кошельки и бабушка были довольны, так что это все о поиске идеальной гармонии / баланса ;-)
Pieter Goosen
очень хороший ответ +1, но стоит сказать, что то, как раз может быть добавлено действие, также зависит от идентификатора обратного вызова, и при работе с объектами вещи немного сложнее ... Трудно лучше объяснить концепцию здесь, я напишу ответ ...
gmazzap
спасибо @gmazzap - да, это было бы здорово, так как я не рассмотрел третий ключ $ idx и _wp_filter_build_unique_id (), просто отобразил его ;-)
birgire
8

Это скорее комментарий к очень хорошему ответу Биргире, чем полный ответ, но из-за необходимости писать код комментарии не подходят.

Из ответа может показаться, что единственной причиной, по которой действие добавляется один раз в пример кода 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 разных идентификатора, в результате чего действие будет добавлено дважды, даже если приоритет одинаков.

Gmazzap
источник
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).

дао
источник