Combien de fois ce code s'exécutera-t-il? (ou, quelle est la richesse de grand-mère?)

20

Exemple hypothétique mais applicabilité dans le monde réel (pour quelqu'un qui apprend, comme moi).

Compte tenu de ce code:

<?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, maintenant je fais apparaître mon site WP et me connecte. Je traverse quelques pages dans Admin. L'action «init» se déclenche 100 fois au total avant que la batterie de mon ordinateur portable ne meure.

Premières questions: combien d'argent avons-nous envoyé à grand-mère? Est-ce 1 $, 2 $, 100 $ ou 200 $ (ou autre chose?)

Si vous pouviez également expliquer votre réponse, ce serait génial.

Deuxième question: si nous voulons nous assurer de n'envoyer que 1 $ à grand-mère, quelle est la meilleure façon de procéder? Variable globale (sémaphore) qui est définie sur "true" la première fois que nous envoyons $ 1? Ou existe-t-il un autre test pour voir si une action s'est déjà produite et l'empêcher de se déclencher plusieurs fois?

Troisième question: est-ce quelque chose qui inquiète les développeurs de plugins? Je me rends compte que mon exemple est stupide mais je pensais à la fois à des problèmes de performances et à d'autres effets secondaires inattendus (par exemple, si la fonction se met à jour / s'insère dans la base de données).

CC
la source
2
Je dois admettre que c'est l'une des meilleures questions depuis longtemps ;-)
Pieter Goosen

Réponses:

21

Voici quelques réflexions aléatoires à ce sujet:

Question 1

Combien d'argent avons-nous envoyé à grand-mère?

Pour 100 chargements de page, nous lui avons envoyé 100 x 1 $ = 100 $.

Ici, nous entendons en fait des 100 x do_action( 'init' )appels.

Peu importe que nous l'ajoutions deux fois avec:

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

car les rappels et les priorités (par défaut 10) sont identiques .

Nous pouvons vérifier comment le add_actionest juste un wrapper pour add_filterconstruire le $wp_filtertableau 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;
}

Si toutefois nous modifions la priorité:

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

alors nous lui enverrions 2 x 1 $ par chargement de page ou 200 $ pour 100 chargements de page.

Même si les rappels sont différents:

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

Question 2

Si nous voulons nous assurer que nous n'envoyons que grand-mère 1 $

Si nous ne voulons l'envoyer qu'une fois par chargement de page , alors cela devrait le faire:

add_action( 'init','send_money_to_grandma' );

parce que le initcrochet n'est tiré qu'une seule fois. Nous pouvons avoir d'autres crochets qui se déclenchent plusieurs fois par chargement de page.

Appelons:

add_action( 'someaction ','send_money_to_grandma' );

mais que se passe-t-il si someaction10 fois par chargement de page se déclenche?

Nous pourrions ajuster la send_money_to_grandma()fonction avec

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

ou utilisez une variable statique comme compteur:

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

Si nous ne voulons l'exécuter qu'une seule fois (jamais!), Nous pouvons alors enregistrer une option dans le wp_optionstableau via l' API Options :

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

Si nous voulons lui envoyer de l'argent une fois par jour, nous pouvons utiliser l' API transitoire

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 même utiliser le wp-cron.

Notez que vous pourriez avoir des appels ajax. ainsi que.

Il existe des moyens de les vérifier, par exemple avec DOING_AJAX

Il peut également y avoir des redirections qui pourraient interrompre le flux.

Ensuite , nous pourrions vouloir limiter au back - end uniquement, is_admin()ou non: ! is_admin().

Question 3

Est-ce quelque chose qui inquiète les développeurs de plugins?

oui c'est important.

Si nous voulons rendre notre grand-mère très heureuse, nous ferions:

add_action( 'all','send_money_to_grandma' );

mais ce serait très mauvais pour les performances ... et notre portefeuille ;-)

Birgire
la source
wow - merci pour une réponse aussi complète; cela aide énormément!
CC
1
Vous êtes les bienvenus - j'ai vraiment apprécié la façon dont vous avez formulé votre question ;-) @CC
birgire
2
À la fin de la journée, nous voulons garder nos portefeuilles et grand-mère heureux, il s'agit donc de trouver l'harmonie / l'équilibre parfait ;-)
Pieter Goosen
très belle réponse +1, mais cela vaut la peine de dire que le nombre de fois qu'une action est ajoutée dépend également de l'id de rappel, et lorsque l'on traite des objets, les choses sont un peu plus complexes ... Difficile de mieux expliquer le concept ici, je ''
Je
merci @gmazzap - oui ce serait génial, car je n'ai pas couvert la troisième clé $ idx et _wp_filter_build_unique_id (), je l'ai juste affichée ;-)
birgire
8

C'est plus un commentaire à la très bonne réponse de Birgire qu'une réponse complète, mais devant écrire du code, les commentaires ne conviennent pas.

D'après la réponse, il peut sembler que la seule raison pour laquelle une action est ajoutée une fois dans l'exemple de code OP, même si elle add_action()est appelée deux fois, est le fait que la même priorité est utilisée. Ce n'est pas vrai.

Dans le code d' add_filterune partie importante se trouve _wp_filter_build_unique_id()l'appel de fonction, qui crée un identifiant unique par rappel .

Si vous utilisez une variable simple, comme une chaîne qui contient un nom de fonction, par exemple "send_money_to_grandma", alors l'id sera égal à la chaîne elle-même, donc si la priorité est la même, étant également la même id, le rappel est ajouté une fois.

Cependant, les choses ne sont pas toujours aussi simples comme ça. Les rappels peuvent être tout ce qui est callableen PHP:

  • noms de fonction
  • méthodes de classe statique
  • méthodes de classe dynamique
  • objets invocables
  • fermetures (fonctions anonymes)

Les deux premiers sont représentés, respectivement, par une chaîne et un tableau de 2 chaînes ( 'send_money_to_grandma'et array('MoneySender', 'send_to_grandma')) de sorte que l'id est toujours le même, et vous pouvez être sûr que le rappel est ajouté une fois si la priorité est la même.

Dans tous les 3 autres cas, l'id dépend des instances d'objet (une fonction anonyme est un objet en PHP) donc le rappel n'est ajouté qu'une seule fois si l'objet est la même instance , et il est important de noter cette même instance et la même classe sont deux choses différentes.

Prenez cet exemple:

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

Combien de dollars envoyons-nous par chargement de page?

La réponse est 2, car l'ID que WordPress génère $sender1et $sender2est différent.

La même chose se produit dans ce cas:

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

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

Ci-dessus, j'ai utilisé la fonction à l' sent_to_grandmaintérieur des fermetures, et même si le code est identique, les 2 fermetures sont 2 instances d' \Closureobjet différentes, donc WP créera 2 identifiants différents, ce qui entraînera l'ajout de l'action deux fois, même si la priorité est la même.

gmazzap
la source
4

Vous ne pouvez pas ajouter la même action au même hook d'action , avec la même priorité .

Cela est fait pour éviter que plusieurs plugins s'appuyant sur une action de plugins tiers ne se produisent plus d'une fois (pensez au woocommerce et à tous ses plugins tiers, comme les intégrations de paiement de passerelle, etc.). Donc sans préciser de priorité, grand-mère reste pauvre:

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

Cependant, si vous ajoutez la priorité à ces actions:

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

Grand-mère meurt maintenant avec 4 $ dans sa poche (1, 2, 3 et la valeur par défaut: 10).

tao
la source