Quantas vezes esse código será executado? (ou quão rica é a vovó?)

20

Exemplo hipotético, mas aplicabilidade no mundo real (para alguém aprendendo, como eu).

Dado este código:

<?php

function send_money_to_grandma() {
     internetofThings("send grandma","$1");
}

add_action('init','send_money_to_grandma');
add_action('init','send_money_to_grandma');

ok, agora eu abro meu site do WP e faço o login. Atravesso algumas páginas no Admin. A ação 'init' dispara um total de 100 vezes antes que minha bateria do laptop morra.

Primeiras perguntas: Quanto dinheiro enviamos para a vovó? São $ 1, $ 2, $ 100 ou $ 200 (ou algo mais?)

Se você também pudesse explicar sua resposta, isso seria incrível.

Segundas perguntas: Se quisermos garantir que apenas enviamos a vovó $ 1, qual é a melhor maneira de fazer isso? Variável global (semáforo) que é definida como 'verdadeira' na primeira vez que enviamos $ 1? Ou há algum outro teste para ver se uma ação já aconteceu e evitar que ela seja disparada várias vezes?

Terceira pergunta: É algo com que os desenvolvedores de plug-ins se preocupam? Eu percebo que meu exemplo é bobo, mas eu estava pensando em problemas de desempenho e outros efeitos colaterais inesperados (por exemplo, se a função é atualizada / inserida no banco de dados).

    
por C C 24.11.2015 / 22:11

3 respostas

21

Aqui estão alguns pensamentos aleatórios sobre isso:

Pergunta # 1

  

Quanto dinheiro enviamos para a vovó?

Para 100 carregamentos de página, enviamos a ela 100 x $ 1 = $ 100.

Aqui, na verdade, queremos dizer 100 x do_action( 'init' ) chamadas.

Não importa que tenhamos adicionado duas vezes com:

add_action( 'init','send_money_to_grandma' );
add_action( 'init','send_money_to_grandma' );

porque os retornos de chamada e prioridades (padrão 10) são idênticos .

Podemos verificar como o add_action é apenas um wrapper para add_filter que constrói a matriz global $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;
}

No entanto, alteramos a prioridade:

add_action( 'init','send_money_to_grandma', 9 );
add_action( 'init','send_money_to_grandma', 10 );

enviaremos para ela 2 x 1 por carregamento de página ou 200 para 100 carregamentos de página.

O mesmo se os retornos de chamada forem diferentes:

add_action( 'init','send_money_to_grandma_1_dollar' );
add_action( 'init','send_money_to_grandma_also_1_dollar' );

Pergunta # 2

  

Se quisermos garantir que apenas enviamos a vovó $ 1

Se quisermos enviá-lo apenas uma vez por carregamento de página , isso deve ser feito:

add_action( 'init','send_money_to_grandma' );

porque o gancho init é acionado apenas uma vez. Podemos ter outros ganchos que são acionados várias vezes por carregamento de página.

Vamos ligar:

add_action( 'someaction ','send_money_to_grandma' );

mas o que acontece se someaction disparar 10 vezes por carregamento de página?

Poderíamos ajustar a função send_money_to_grandma() com

function send_money_to_grandma() 
{
    if( ! did_action( 'someaction' ) )
        internetofThings("send grandma","$1");
}

ou use uma variável static como um contador:

function send_money_to_grandma() 
{
    static $counter = 0;
    if( 0 === $counter++ )
        internetofThings("send grandma","$1");
}

Se desejarmos executá-lo apenas uma vez (sempre!), poderemos registrar uma opção na tabela wp_options por meio da API de opções :

function send_money_to_grandma() 
{
    if( 'no' === get_option( 'sent_grandma_money', 'no' ) )
    {
        update_option( 'sent_grandma_money', 'yes' );
        internetofThings( "send grandma","$1" );
    }
}

Se quisermos enviar o dinheiro dela uma vez por dia, podemos usar a API temporária

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

ou até mesmo use o wp-cron.

Note que você pode ter chamadas ajax. também.

Existem maneiras de verificar essas, por exemplo com DOING_AJAX

Também pode haver redirecionamentos que podem interromper o fluxo.

Então, poderíamos restringir apenas ao back-end, is_admin() ou não: ! is_admin() .

Pergunta # 3

  

Isso é algo com que os desenvolvedores de plug-ins se preocupam?

sim, isso é importante.

Se quisermos fazer nossa avó muito feliz, faríamos:

add_action( 'all','send_money_to_grandma' );

mas isso seria muito ruim para o desempenho ... e nossa carteira; -)

    
por birgire 24.11.2015 / 23:31
8

Este é mais um comentário à muito boa resposta da Birgire do que uma resposta completa, mas ter que escrever código , os comentários não se encaixam.

A partir da resposta, parece que a única razão pela qual a ação é incluída uma vez no código de amostra OP, mesmo se add_action() for chamado duas vezes, é o fato de que a mesma prioridade é usada. Isso não é verdade.

No código de add_filter , uma parte importante é a chamada de função _wp_filter_build_unique_id() , que cria uma ID exclusivo por retorno de chamada .

Se você usar uma variável simples, como uma string que contém um nome de função, por exemplo, "send_money_to_grandma" , então o id será igual ao próprio string, então se a prioridade for a mesma, sendo id o mesmo, o callback é adicionado uma vez.

No entanto, as coisas nem sempre são assim tão simples. Callbacks podem ser qualquer coisa que seja callable no PHP:

  • nomes de funções
  • métodos de classe estática
  • métodos de classe dinâmica
  • objetos invocáveis
  • encerramentos (funções anônimas)

Os dois primeiros são representados, respectivamente, por uma string e uma matriz de 2 strings ( 'send_money_to_grandma' e array('MoneySender', 'send_to_grandma') ), então o id é sempre o mesmo, e você pode ter certeza de que o callback é adicionado uma vez se a prioridade for o mesmo.

Em todos os outros 3 casos, o id depende das instâncias do objeto (uma função anônima é um objeto no PHP), então o retorno de chamada é adicionado apenas uma vez se o objeto for o mesmo instance , e É importante notar que mesma instância e mesma classe são duas coisas diferentes.

Veja este exemplo:

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

Quantos dólares estamos enviando por carregamento de página?

A resposta é 2, porque o ID que o WordPress gera para $sender1 e $sender2 é diferente.

O mesmo acontece neste caso:

add_action( 'init', function() {
   sent_to_grandma();
} );

add_action( 'init', function() {
   sent_to_grandma();
} );

Acima eu usei a função sent_to_grandma dentro de closures, e mesmo que o código seja idêntico, os 2 closures são 2 instâncias diferentes de \Closure object, então WP criará 2 ids diferentes, o que fará com que a ação seja adicionada duas vezes, mesmo que a prioridade seja a mesma.

    
por gmazzap 25.11.2015 / 13:42
4

Você não pode adicionar a mesma ação ao mesmo gancho de ação , com a mesma prioridade .

Isso é feito para evitar que vários plug-ins que dependem da ação de plugins de terceiros aconteçam mais de uma vez (pense no woocommerce e em todos os plug-ins de terceiros, como integrações de pagamento de gateway etc.). Então, sem especificar prioridade, a avó continua pobre:

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

No entanto, se você adicionar prioridade a essas ações:

add_action('init','print_a_buck', 1);
add_action('init','print_a_buck', 2);
add_action('init','print_a_buck', 3);

Agora a avó morre com $ 4 no bolso (1, 2, 3 e o padrão: 10).

    
por Andrei Gheorghiu 24.11.2015 / 23:27