Question

Je démarre une nouvelle application Web en PHP et cette fois-ci, je souhaite créer quelque chose que les gens peuvent étendre en utilisant une interface de plugin.

Comment peut-on écrire des « hooks » dans leur code afin que les plugins puissent s'attacher à des événements spécifiques ?

Était-ce utile?

La solution

Vous pouvez utiliser un modèle Observer.Un moyen fonctionnel simple d'y parvenir :

<?php

/** Plugin system **/

$listeners = array();

/* Create an entry point for plugins */
function hook() {
    global $listeners;

    $num_args = func_num_args();
    $args = func_get_args();

    if($num_args < 2)
        trigger_error("Insufficient arguments", E_USER_ERROR);

    // Hook name should always be first argument
    $hook_name = array_shift($args);

    if(!isset($listeners[$hook_name]))
        return; // No plugins have registered this hook

    foreach($listeners[$hook_name] as $func) {
        $args = $func($args); 
    }
    return $args;
}

/* Attach a function to a hook */
function add_listener($hook, $function_name) {
    global $listeners;
    $listeners[$hook][] = $function_name;
}

/////////////////////////

/** Sample Plugin **/
add_listener('a_b', 'my_plugin_func1');
add_listener('str', 'my_plugin_func2');

function my_plugin_func1($args) {
    return array(4, 5);
}

function my_plugin_func2($args) {
    return str_replace('sample', 'CRAZY', $args[0]);
}

/////////////////////////

/** Sample Application **/

$a = 1;
$b = 2;

list($a, $b) = hook('a_b', $a, $b);

$str  = "This is my sample application\n";
$str .= "$a + $b = ".($a+$b)."\n";
$str .= "$a * $b = ".($a*$b)."\n";

$str = hook('str', $str);
echo $str;
?>

Sortir:

This is my CRAZY application
4 + 5 = 9
4 * 5 = 20

Remarques:

Pour cet exemple de code source, vous devez déclarer tous vos plugins avant le code source réel que vous souhaitez étendre.J'ai inclus un exemple de la façon de gérer une ou plusieurs valeurs transmises au plugin.La partie la plus difficile est d'écrire la documentation proprement dite qui répertorie les arguments transmis à chaque hook.

Ce n’est qu’une méthode parmi d’autres pour réaliser un système de plugins en PHP.Il existe de meilleures alternatives, je vous suggère de consulter la documentation WordPress pour plus d'informations.

Désolé, il semble que les caractères de soulignement soient remplacés par des entités HTML par Markdown ?Je pourrai republier ce code lorsque ce bug sera corrigé.

Modifier:Peu importe, cela n'apparaît ainsi que lorsque vous éditez

Autres conseils

Supposons donc que vous ne vouliez pas du modèle Observer car il nécessite que vous changiez vos méthodes de classe pour gérer la tâche d'écoute et que vous souhaitiez quelque chose de générique.Et disons que vous ne voulez pas utiliser extends héritage car vous héritez peut-être déjà dans votre classe d'une autre classe.Ne serait-il pas génial d'avoir une manière générique de créer n'importe quelle classe peut être branchée sans trop d'effort?Voici comment:

<?php

////////////////////
// PART 1
////////////////////

class Plugin {

    private $_RefObject;
    private $_Class = '';

    public function __construct(&$RefObject) {
        $this->_Class = get_class(&$RefObject);
        $this->_RefObject = $RefObject;
    }

    public function __set($sProperty,$mixed) {
        $sPlugin = $this->_Class . '_' . $sProperty . '_setEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }   
        $this->_RefObject->$sProperty = $mixed;
    }

    public function __get($sProperty) {
        $asItems = (array) $this->_RefObject;
        $mixed = $asItems[$sProperty];
        $sPlugin = $this->_Class . '_' . $sProperty . '_getEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }   
        return $mixed;
    }

    public function __call($sMethod,$mixed) {
        $sPlugin = $this->_Class . '_' .  $sMethod . '_beforeEvent';
        if (is_callable($sPlugin)) {
            $mixed = call_user_func_array($sPlugin, $mixed);
        }
        if ($mixed != 'BLOCK_EVENT') {
            call_user_func_array(array(&$this->_RefObject, $sMethod), $mixed);
            $sPlugin = $this->_Class . '_' . $sMethod . '_afterEvent';
            if (is_callable($sPlugin)) {
                call_user_func_array($sPlugin, $mixed);
            }       
        } 
    }

} //end class Plugin

class Pluggable extends Plugin {
} //end class Pluggable

////////////////////
// PART 2
////////////////////

class Dog {

    public $Name = '';

    public function bark(&$sHow) {
        echo "$sHow<br />\n";
    }

    public function sayName() {
        echo "<br />\nMy Name is: " . $this->Name . "<br />\n";
    }


} //end class Dog

$Dog = new Dog();

////////////////////
// PART 3
////////////////////

$PDog = new Pluggable($Dog);

function Dog_bark_beforeEvent(&$mixed) {
    $mixed = 'Woof'; // Override saying 'meow' with 'Woof'
    //$mixed = 'BLOCK_EVENT'; // if you want to block the event
    return $mixed;
}

function Dog_bark_afterEvent(&$mixed) {
    echo $mixed; // show the override
}

function Dog_Name_setEvent(&$mixed) {
    $mixed = 'Coco'; // override 'Fido' with 'Coco'
    return $mixed;
}

function Dog_Name_getEvent(&$mixed) {
    $mixed = 'Different'; // override 'Coco' with 'Different'
    return $mixed;
}

////////////////////
// PART 4
////////////////////

$PDog->Name = 'Fido';
$PDog->Bark('meow');
$PDog->SayName();
echo 'My New Name is: ' . $PDog->Name;

Dans la première partie, c'est ce que vous pourriez inclure avec un require_once() appelez en haut de votre script PHP.Il charge les classes pour créer quelque chose de connectable.

Dans la partie 2, c'est là que nous chargeons une classe.Notez que je n'ai rien eu à faire de spécial pour la classe, ce qui est très différent du modèle Observer.

Dans la partie 3, c'est là que nous transformons notre classe en "pluggable" (c'est-à-dire qu'elle prend en charge les plugins qui nous permettent de remplacer les méthodes et les propriétés de la classe).Ainsi, par exemple, si vous avez une application Web, vous pourriez avoir un registre de plugins et vous pouvez activer les plugins ici.Remarquez également le Dog_bark_beforeEvent() fonction.Si je mets $mixed = 'BLOCK_EVENT' avant l'instruction return, cela empêchera le chien d'aboyer et bloquera également Dog_bark_afterEvent car il n'y aurait aucun événement.

Dans la partie 4, c'est le code de fonctionnement normal, mais notez que ce que vous pourriez penser fonctionner ne fonctionne pas du tout comme ça.Par exemple, le chien n'annonce pas son nom comme « Fido », mais comme « Coco ».Le chien ne dit pas « miaou », mais « Woof ».Et quand vous voulez regarder le nom du chien par la suite, vous constatez qu'il s'agit de « Différent » au lieu de « Coco ».Toutes ces dérogations ont été fournies dans la partie 3.

Alors, comment ça marche?Eh bien, excluons eval() (ce que tout le monde dit être "maléfique") et excluez qu'il ne s'agisse pas d'un modèle d'observateur.Ainsi, la façon dont cela fonctionne est la classe vide et sournoise appelée Pluggable, qui ne contient pas les méthodes et propriétés utilisées par la classe Dog.Ainsi, puisque cela se produit, les méthodes magiques s'engageront pour nous.C'est pourquoi, dans les parties 3 et 4, nous manipulons l'objet dérivé de la classe Pluggable, et non la classe Dog elle-même.Au lieu de cela, nous laissons la classe Plugin faire le "toucher" sur l'objet Dog pour nous.(S'il s'agit d'une sorte de modèle de conception que je ne connais pas, veuillez me le faire savoir.)

Le crochet et auditeur La méthode est la plus couramment utilisée, mais vous pouvez faire d’autres choses.En fonction de la taille de votre application et de la personne que vous allez autoriser à voir le code (s'agira-t-il d'un script FOSS ou de quelque chose en interne) influencera grandement la façon dont vous souhaitez autoriser les plugins.

kdeloach a un bon exemple, mais son implémentation et sa fonction hook sont un peu dangereuses.Je vous demanderais de donner plus d'informations sur la nature de l'application php que vous écrivez et sur la façon dont vous voyez les plugins s'intégrer.

+1 à kdeloach de ma part.

Voici une approche que j'ai utilisée, c'est une tentative de copier le mécanisme des signaux/slots de Qt, une sorte de modèle Observer.Les objets peuvent émettre des signaux.Chaque signal a un ID dans le système - il est composé par ID de l'expéditeur + nom d'objet Chaque signal peut être reproduit aux récepteurs, ce qui est simplement un "callable" que vous utilisez un cours de bus pour transmettre les signaux à toute personne intéressée à les recevoir lorsque quelque chose Cela se produit, vous "envoyez" un signal.Vous trouverez ci-dessous un exemple de mise en œuvre

    <?php

class SignalsHandler {


    /**
     * hash of senders/signals to slots
     *
     * @var array
     */
    private static $connections = array();


    /**
     * current sender
     *
     * @var class|object
     */
    private static $sender;


    /**
     * connects an object/signal with a slot
     *
     * @param class|object $sender
     * @param string $signal
     * @param callable $slot
     */
    public static function connect($sender, $signal, $slot) {
        if (is_object($sender)) {
            self::$connections[spl_object_hash($sender)][$signal][] = $slot;
        }
        else {
            self::$connections[md5($sender)][$signal][] = $slot;
        }
    }


    /**
     * sends a signal, so all connected slots are called
     *
     * @param class|object $sender
     * @param string $signal
     * @param array $params
     */
    public static function signal($sender, $signal, $params = array()) {
        self::$sender = $sender;
        if (is_object($sender)) {
            if ( ! isset(self::$connections[spl_object_hash($sender)][$signal])) {
                return;
            }
            foreach (self::$connections[spl_object_hash($sender)][$signal] as $slot) {
                call_user_func_array($slot, (array)$params);
            }

        }
        else {
            if ( ! isset(self::$connections[md5($sender)][$signal])) {
                return;
            }
            foreach (self::$connections[md5($sender)][$signal] as $slot) {
                call_user_func_array($slot, (array)$params);
            }
        }

        self::$sender = null;
    }


    /**
     * returns a current signal sender
     *
     * @return class|object
     */
    public static function sender() {
        return self::$sender;
    }

}   

class User {

    public function login() {
        /**
         * try to login
         */
        if ( ! $logged ) {
            SignalsHandler::signal(this, 'loginFailed', 'login failed - username not valid' );
        }
    }

}

class App {
    public static function onFailedLogin($message) {
        print $message;
    }
}


$user = new User();
SignalsHandler::connect($user, 'loginFailed', array($Log, 'writeLog'));
SignalsHandler::connect($user, 'loginFailed', array('App', 'onFailedLogin'));

$user->login();

?>

Je pense que le moyen le plus simple serait de suivre les propres conseils de Jeff et de jeter un œil au code existant.Essayez de consulter Wordpress, Drupal, Joomla et d'autres CMS bien connus basés sur PHP pour voir à quoi ressemblent leurs hooks API.De cette façon, vous pouvez même trouver des idées auxquelles vous n'aviez peut-être pas pensé auparavant pour rendre les choses un peu plus robustes.

Une réponse plus directe serait d'écrire des fichiers généraux qu'ils "include_once" dans leur fichier et qui fourniraient la convivialité dont ils auraient besoin.Cela serait divisé en catégories et NON fourni dans un seul fichier MASSIF "hooks.php".Soyez prudent cependant, car ce qui finit par arriver, c'est que les fichiers qu'ils incluent finissent par avoir de plus en plus de dépendances et que les fonctionnalités s'améliorent.Essayez de maintenir les dépendances à l'API à un niveau faible.C'est-à-dire moins de fichiers à inclure.

Il y a un projet intéressant appelé Épinoche par Matt Zandstra chez Yahoo qui gère une grande partie du travail de gestion des plugins en PHP.

Il applique l'interface d'une classe de plugin, prend en charge une interface de ligne de commande et n'est pas trop difficile à démarrer - surtout si vous lisez l'article de couverture à ce sujet dans le Revue d'architecte PHP.

Un bon conseil est de regarder comment d’autres projets l’ont fait.Beaucoup demandent que les plugins soient installés et que leur "nom" soit enregistré pour les services (comme le fait WordPress), vous avez donc des "points" dans votre code où vous appelez une fonction qui identifie les auditeurs enregistrés et les exécute.Un modèle de conception OO standard est le Modèle d'observateur, ce qui serait une bonne option à implémenter dans un système PHP véritablement orienté objet.

Le Cadre Zend utilise de nombreuses méthodes d'accrochage et est très bien architecturé.Ce serait un bon système à examiner.

Je suis surpris que la plupart des réponses ici semblent concerner les plugins locaux de l'application Web, c'est-à-dire les plugins qui s'exécutent sur le serveur Web local.

Et si vous vouliez que les plugins s'exécutent sur un autre serveur - distant - ?La meilleure façon de procéder serait de fournir un formulaire vous permettant de définir différentes URL qui seraient appelées lorsque des événements particuliers se produisent dans votre application.

Différents événements enverraient des informations différentes en fonction de l'événement qui vient de se produire.

De cette façon, vous effectueriez simplement un appel cURL à l'URL qui a été fournie à votre application (par exemple via https) où les serveurs distants peuvent effectuer des tâches basées sur les informations envoyées par votre application.

Cela offre deux avantages :

  1. Vous n'êtes pas obligé d'héberger du code sur votre serveur local (sécurité)
  2. Le code peut être sur des serveurs distants (extensibilité) dans différents langages autres que PHP (portabilité)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top