Page 1 sur 1

[PHP] Class hook (l'art de lancer plusieurs traitements)

Posté : 20 oct. 2010, 20:45
par xTG
Je souhaitai développer une class permettant de faire du hook, à savoir lancer un second (voir X traitements) en plus de la fonction appelée et ce de manière presque transparente. Voici le fil que j'avais ouvert lors du développement de cette classe : php-oriente-objet/hook-fonctions-t255592.html

Certains frameworks possèdent ce genre de traitement au travers d'une construction de leurs fonction de la sorte :

Code : Tout sélectionner

fonction ma_fonction(paramètres) { appel fonction hook début si elle existe exécution de la fonction "ma_fonction" appel fonction hook fin si elle existe }
C'est donc un ajout direct dans le code de toutes les fonctions et cela ne me plaisait pas.

Je me suis donc penché sur comment faire une class dont on hériterai et qui pourrai gérer l'appel d'un autre traitement.
Le procédé final n'est pas génial (faut aussi admettre que PHP ne donne aucun atout véritable pour gérer ce type de fonctionnement...) et pourra certes être contesté. Je l'ai développé pour mes propres besoins (et ajouté un ou deux trucs qui me serviront sans doute jamais mais qui vous aideront peut être) donc je ne m'amuserai pas à tout re-modifier sans que cela ne m'apporte quelque chose à mon problème de départ. Cela dit vous êtes libre de repartir sur ces bases et de la modifier à votre convenance.
class hook
{
    /*
    * Liste des emplacements hook actifs
    * @type array(String => Boolean)
    */
    protected $tab_hook_nom = array('debut' => true, 'fin' => true);
    
    /*
    * Liste des fonctions hook
    * @type array(Mixed)
    */
    protected $tab_hook_fonction = array();
    
    /*
    * Instance fille
    * @type Object
    */
    protected $instance_fille = null;
    
    /*
    * Constructeur
    * @type Object objet héritant de la classe Hook
    */
    public function __construct($instance_fille)
    {
        $this->instance_fille = $instance_fille;
    }
    
    /*
    * Appel de fonction
    */
    public function __call($name, $arguments)
    {
        // Gestion de l'appel du plus bas niveau de l'héritage
        if( !empty($this->instance_fille) )
            $fonction_appel = array($this->instance_fille, $name);
        else
            $fonction_appel = array($this, $name);
        
        // On vérifie que la méthode appelée existe
        if( method_exists($fonction_appel[0],$name) )
        {
            // On prépare les arguments pour les fonctions hook
            $arguments_hook[0] = $name;
            $arguments_hook[1] = $arguments;
            
            // Appel des hooks de debut
            if( !empty($this->tab_hook_nom['debut']) && !empty($this->tab_hook_fonction['debut'][$name]) )
            {
                foreach($this->tab_hook_fonction['debut'][$name] as $fonction)
                    call_user_func_array($fonction, $arguments_hook);
            }
            
            // Appel de la fonction
            $retour = call_user_func_array($fonction_appel, $arguments);
            
            // Appel des hooks de fin
            if( !empty($this->tab_hook_nom['fin']) && !empty($this->tab_hook_fonction['fin'][$name]) )
            {
                foreach($this->tab_hook_fonction['fin'][$name] as $fonction)
                {
                    $arguments_hook[2] = $retour;
                    $retour_tmp = call_user_func_array($fonction, $arguments_hook);
                    if( isSet($retour_tmp) ) // vérification que la fonction renvoie bien une valeur
                        $retour = $retour_tmp;
                }
            }
            
            // Retour du résultat
            return $retour;
        }
    }
    
    /*
    * Méthode ajoutant un hook sur une fonction de la classe
    * @type String emplacement du hook
    * @type String fonction ciblée par le hook
    * @type String/array(mixed) fonction appelée lors du hook
    *        string => nom de la fonction
    *        array(mixed) => array( Object, String) => array( objet instancié, nom de la fonction de l'objet)
    * @return Boolean
    */
    protected function ajouter_hook($nom, $fonction, $fonction_hook)
    {
        if( !empty($this->tab_hook_nom[$nom]) )
        {
            $this->tab_hook_fonction[$nom][$fonction][] = $fonction_hook;
            return true;
        }
        else
            return false;
    }
    
    /*
    * Méthode supprimant un hook sur une fonction de la classe
    * @type String emplacement du hook
    * @type String fonction ciblée par le hook
    * @type String fonction appelée lors du hook
    */
    protected function supprimer_hook($nom, $fonction, $fonction_hook)
    {
        if( !empty($this->tab_hook_nom[$nom]) )
        {
            if( !empty($this->tab_hook_fonction[$nom][$fonction]) )
            {
                foreach($this->tab_hook_fonction[$nom][$fonction] as $key => $fct)
                {
                    if( (is_array($fct) && is_array($fonction_hook)) || (is_string($fct) && is_string($fonction_hook)) )
                    if( $fct == $fonction_hook )
                    {
                        unset($this->tab_hook_fonction[$nom][$fonction][$key]);
                        break;
                    }
                }
            }
        }
    }
    public function __toString()
    {
        return get_class();
    }
}
Point positif du système :
  • La classe qui hérite de Hook ne change en rien de code, toutes vos fonctions restent les mêmes
  • On peut appeler plusieurs fonctions avant et après la fonction appelée, c'est un tableau de fonction qui est géré.
Point négatif :
  • Pour que le système s'applique il faut que vos méthodes soient protected afin que la méthode __call() de la classe Hook puisse être lancée. La classe se base sur le fait que la fonction ne puisse être appelée de par la portée pour exécuter la fonction __call() qui est une fonction PHP d'erreur.
  • Il faut jouer un peu avec le constructeur si vous faites plusieurs héritages (voir le constructeur de la classe Test) afin d'enregistrer le dernier fils de l'héritage pour palier aux soucis d'appels statique.
Voici une classe d'essai ainsi que son code associé, le test est très simpliste mais cela donne un vague aperçu de ce que cela permet de faire :
class test extends hook
{    
    /*
    * @type Object
    */
    public function __construct($fils = null)
    {
        if( !empty($fils) )
            parent::__construct($fils);
        else
            parent::__construct($this);
    }
    
    protected function fonction1()
    {
        echo "exécution fonction1<br />";
    }
    protected function fonction2($nb1,$nb2,$nb3)
    {
        echo "exécution fonction2 : $nb1, $nb2, $nb3<br/>";
        return $nb1 + $nb2 + $nb3;
    }
    public function __toString()
    {
        return get_class();
    }
}

$i = 0;

function mon_hook($fonction, $arguments)
{
    global $i;
    $i++;
}
function mon_hook2($fonction, $arguments, $retour)
{
    global $i;
    $i += 2;
    echo "$retour + 2(hook)";
    return $retour + 2;
}

$test = new test();
$test->ajouter_hook("debut","fonction2",array($test,"fonction1"));
$test->ajouter_hook("fin","fonction2","mon_hook");
echo $test->fonction2(1,2,3) . "<br />";
echo $i;
exécution fonction1
exécution fonction2 : 1, 2, 3
6
1
En espérant que cela serve à quelqu'un. :)

Re: [PHP] Class hook

Posté : 20 oct. 2010, 23:46
par AB
Merci pour cette contribution, je ne l'ai pas testée mais j'avais suivi le post original et c'est bien d'avoir été jusqu'au bout de ce problème et de faire partager ta solution.

Juste une petite suggestion sur la forme : ne pourrais-tu pas compléter ton titre ("Class hook") pour qu'il soit plus causant pour le commun des mortels ? Il te reste pas mal de place pour ajouter d'autres mots à la suite qui pourraient être plus signifiants pour tout le monde... :)


Je dis ça car j'ai été lire ton précédent post par curiosité mais je me demandais bien ce dont il s'agissait au départ (faut dire aussi que j'ai pas eu pour l'instant à gérer ce genre de pb)... et pour être complet dans mes petites remarques, penser à mettre un lien de ton message précédent vers ce post :)

Re: [PHP] Class hook (l'art de lancer plusieurs traitements)

Posté : 21 oct. 2010, 07:13
par xTG
Titre modifié (peut pas faire mieux il reste que deux caractères de libres :D ) et rajout d'url vers le topic de développement et vers une définition du terme sur wikipedia.