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


Гипотетический пример, но применимость в реальном мире (для кого-то, кто учится, как я).

Учитывая этот код:

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

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

Author: C C, 2015-11-24

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' );

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

 21
Author: birgire, 2015-11-24 22:52:43

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

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

 8
Author: gmazzap, 2017-04-13 12:37:50

Вы не можете добавить то же действие в тот же крючок действия с тем же приоритетом.

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

 4
Author: tao, 2015-11-24 22:36:36