¿Cuántas veces se ejecutará este código? (o, ¿qué tan rica es la abuela?)

20

Ejemplo hipotético pero aplicabilidad en el mundo real (para alguien que está aprendiendo, como yo).

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, ahora abro mi sitio WP e inicio sesión. Recorro algunas páginas en Admin. La acción 'init' dispara un total de 100 veces antes de que se agote la batería de mi computadora portátil.

Primeras preguntas ¿Cuánto dinero le enviamos a la abuela? ¿Son $ 1, $ 2, $ 100 o $ 200 (o algo más?)

Si también pudieras explicar tu respuesta, sería increíble.

Segunda pregunta: si queremos asegurarnos de que solo le enviemos $ 1 a la abuela, ¿cuál es la mejor manera de hacerlo? ¿Variable global (semáforo) que se establece como 'verdadera' la primera vez que enviamos $ 1? ¿O hay alguna otra prueba para ver si ya ocurrió una acción y evitar que se dispare varias veces?

Tercera pregunta: ¿Es esto algo de lo que se preocupan los desarrolladores de complementos? Me doy cuenta de que mi ejemplo es tonto, pero estaba pensando en problemas de rendimiento y otros efectos secundarios inesperados (por ejemplo, si la función se actualiza / inserta en la base de datos).

CC
fuente
2
Debo admitir, esta es una de las mejores preguntas en mucho tiempo ;-)
Pieter Goosen

Respuestas:

21

Aquí hay algunos pensamientos al azar sobre esto:

Pregunta 1

¿Cuánto dinero le enviamos a la abuela?

Para 100 cargas de página, le enviamos 100 x $ 1 = $ 100.

Aquí en realidad queremos decir 100 x do_action( 'init' )llamadas.

No importaba que lo añadiéramos dos veces con:

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

porque las devoluciones de llamada y las prioridades (por defecto 10) son idénticas .

Podemos verificar cómo add_actiones solo un contenedor para add_filterque construya la $wp_filtermatriz global :

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

Sin embargo, si cambiamos la prioridad:

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

entonces le enviaríamos 2 x $ 1 por carga de página o $ 200 por 100 cargas de página.

Lo mismo si las devoluciones de llamada son diferentes:

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

Pregunta 2

Si queremos asegurarnos de que solo le enviemos a la abuela $ 1

Si solo queremos enviarlo una vez por carga de página , esto debería hacerlo:

add_action( 'init','send_money_to_grandma' );

porque el init gancho solo se dispara una vez. Es posible que tengamos otros ganchos que se activan muchas veces por carga de página.

Llamemos:

add_action( 'someaction ','send_money_to_grandma' );

pero que pasa si someaction dispara 10 veces por carga de página?

Podríamos ajustar la send_money_to_grandma()función con

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

o use una variable estática como contador:

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

Si solo queremos ejecutarlo una vez (¡alguna vez!), Entonces podríamos registrar una opción en la wp_optionstabla a través de la API de Opciones :

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

Si queremos enviarle dinero una vez al día, entonces podemos usar la API transitoria

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

o incluso usar el wp-cron.

Tenga en cuenta que puede tener llamadas ajax. también.

Hay formas de verificarlos, por ejemplo, con DOING_AJAX

También puede haber redirecciones, que podrían interrumpir el flujo.

Entonces podríamos querer restringir solo al backend, is_admin()o no:! is_admin() .

Pregunta 3

¿Es esto algo de lo que se preocupan los desarrolladores de complementos?

Sí, esto es importante.

Si queremos hacer muy feliz a nuestra abuela, haríamos:

add_action( 'all','send_money_to_grandma' );

pero esto sería muy malo para el rendimiento ... y nuestra billetera ;-)

Birgire
fuente
wow, gracias por una respuesta tan completa; Esto ayuda inmensamente!
CC
1
De nada
birgire
2
Al final del día, queremos mantener nuestras billeteras y abuela felices, por lo que se trata de encontrar la armonía / equilibrio perfecto ;-)
Pieter Goosen
Muy buena respuesta +1, pero vale la pena decir que cuántas veces se agrega una acción también depende de la identificación de devolución de llamada, y cuando se trata de objetos las cosas son un poco más complejas ... Es difícil explicar mejor el concepto aquí, yo ' escribiré una respuesta ...
gmazzap
gracias @gmazzap - sí, eso sería genial, ya que no cubrí la tercera clave $ idx y _wp_filter_build_unique_id (), solo la
mostré
8

Esto es más un comentario a la muy buena respuesta de Birgire que una respuesta completa, pero al tener que escribir el código, los comentarios no encajan.

De la respuesta puede parecer que la única razón por la que se agrega acción una vez en el código de muestra OP, incluso si add_action()se llama dos veces, es el hecho de que se utiliza la misma prioridad. Eso no es cierto.

En el código de add_filteruna parte importante está la _wp_filter_build_unique_id()función call, que crea una identificación única por devolución de llamada .

Si usa una variable simple, como una cadena que contiene un nombre de función, por ejemplo "send_money_to_grandma" , la identificación será igual a la cadena en sí, por lo que si la prioridad es la misma, siendo la misma también, la devolución de llamada se agrega una vez.

Sin embargo, las cosas no siempre son así de simples. Las devoluciones de llamada pueden ser cualquier cosa que esté callableen PHP:

  • nombres de funciones
  • métodos de clase estática
  • métodos de clase dinámica
  • objetos invocables
  • cierres (funciones anónimas)

Los dos primeros están representados, respectivamente, por una cadena y una matriz de 2 cadenas ( 'send_money_to_grandma'y array('MoneySender', 'send_to_grandma')) por lo que la identificación es siempre la misma, y ​​puede estar seguro de que la devolución de llamada se agrega una vez si la prioridad es la misma.

En los otros 3 casos, la identificación depende de las instancias del objeto (una función anónima es un objeto en PHP), por lo que la devolución de llamada se agrega una vez solo si el objeto es la misma instancia , y es importante tener en cuenta esa misma instancia y la misma clase Son dos cosas diferentes.

Toma este ejemplo:

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

¿Cuántos dólares enviamos por carga de página?

La respuesta es 2, porque la identificación que WordPress genera $sender1y $sender2es diferente.

Lo mismo sucede en este caso:

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

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

Arriba utilicé la función sent_to_grandmadentro de los cierres, e incluso si el código es idéntico, los 2 cierres son 2 instancias diferentes de \Closureobjeto, por lo que WP creará 2 identificadores diferentes, lo que hará que la acción se agregue dos veces, incluso si la prioridad es la misma.

gmazzap
fuente
4

No puedes agregar el misma acción al mismo enlace de acción , con la misma prioridad .

Esto se hace para evitar que varios complementos que se basan en la acción de un complemento de terceros sucedan más de una vez (piense en woocommerce y todos sus complementos de terceros, como las integraciones de pago de pasarela, etc.). Entonces, sin especificar la prioridad, la abuela sigue siendo 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');
}

Sin embargo, si agrega prioridad a esas acciones:

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

La abuela ahora muere con $ 4 en su bolsillo (1, 2, 3 y el valor predeterminado: 10).

tao
fuente