Meilleure façon d'autoriser les plugins pour une application PHP

Translate

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 écrire des «hooks» dans leur code afin que les plugins puissent s'attacher à des événements spécifiques?

This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

Vous pouvez utiliser un modèle Observer. Une manière fonctionnelle simple d'accomplir ceci:

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

Production:

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 manière de gérer une ou plusieurs valeurs transmises au plugin. La partie la plus difficile de ceci est d'écrire la documentation réelle qui répertorie les arguments passés à chaque hook.

Ce n'est qu'une méthode pour réaliser un système de plugin 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 peux republier ce code lorsque ce bogue sera corrigé.

Edit: Nevermind, cela n'apparaît que lorsque vous éditez

La source
Translate

Disons que vous ne voulez 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 vouliez quelque chose de générique. Et disons que tu ne veux pas utiliserextendsl'héritage parce que vous héritez peut-être déjà de votre classe d'une autre classe. Ne serait-il pas génial d'avoir un moyen générique de fairetoute classe enfichable 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 partie 1, c'est ce que vous pourriez inclure avec unrequire_once()appel en haut de votre script PHP. Il charge les classes pour rendre quelque chose enfichable.

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

Dans la partie 3, c'est là que nous changeons notre classe pour qu'elle soit "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 classe). Ainsi, par exemple, si vous avez une application Web, vous pouvez avoir un registre de plugins et vous pouvez activer les plugins ici. Remarquez également leDog_bark_beforeEvent()fonction. Si je règle$mixed = 'BLOCK_EVENT'avant l'instruction return, cela empêchera le chien d'aboyer et bloquera également l'événement 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 que fonctionnerait ne fonctionne pas du tout comme ça. Par exemple, le chien n'annonce pas son nom comme «Fido», mais «Coco». Le chien ne dit pas «miaou», mais «Woof». Et quand vous voulez regarder le nom du chien par la suite, vous trouvez qu'il est «différent» au lieu de «Coco». Toutes ces dérogations ont été fournies dans la partie 3.

Donc comment ça fonctionne? Eh bien, excluonseval()(ce que tout le monde dit est "mal") et exclure que ce n'est pas un modèle d'observateur. Donc, la façon dont cela fonctionne est la classe vide sournoise appelée Pluggable, qui ne contient pas les méthodes et les 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 gâchons avec l'objet dérivé de la classe Pluggable, pas la classe Dog elle-même. Au lieu de cela, nous laissons la classe Plugin faire le "toucher" sur l'objet Dog pour nous. (Si c'est une sorte de modèle de conception que je ne connais pas, faites-le moi savoir.)

La source
Translate

lecrochetetauditeurest la méthode 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 (est-ce que cela va être un script FOSS, ou quelque chose en interne) influencera grandement la façon dont vous souhaitez autoriser les plugins.

kdeloach a un bel 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 votre écriture php et sur la façon dont vous voyez les plugins s'intégrer.

+1 à kdeloach de ma part.

La source
Translate

Voici une approche que j'ai utilisée, c'est une tentative de copier à partir du mécanisme signaux / slots Qt, une sorte de pattern Observer. Les objets peuvent émettre des signaux. Chaque signal a un identifiant dans le système - il est composé de l'identifiant de l'expéditeur + nom de l'objet Chaque signal peut être lié aux récepteurs, ce qui est simplement un "appelable" Vous utilisez une classe de bus pour transmettre les signaux à toute personne intéressée à les recevoir Quand quelque chose arrive, vous «envoyez» un signal. Voici 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();

?>
La source
Translate

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

Une réponse plus directe serait d'écrire des fichiers généraux qu'ils "incluraient une fois" dans leur fichier, ce qui leur fournirait la convivialité dont ils auraient besoin. Cela serait divisé en catégories et NON fourni dans un fichier MASSIVE "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 garder les dépendances d'API faibles. IE moins de fichiers à inclure.

La source
Translate

Il y a un beau projet appeléÉpinochepar 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 plug-in, 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 leMagazine des architectes PHP.

La source
Translate

Le bon conseil est de regarder comment d'autres projets l'ont fait. Beaucoup demandent que des plugins soient installés et que leur «nom» soit enregistré pour les services (comme le fait wordpress), donc vous avez 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 leModèle d'observateur, ce qui serait une bonne option à implémenter dans un système PHP véritablement orienté objet.

leCadre Zendutilise de nombreuses méthodes d'accrochage et est très joliment architecturé. Ce serait un bon système à examiner.

La source
Translate

Je suis surpris que la plupart des réponses ici semblent être axées sur 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 qui vous permet 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 effectuerez 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 en fonction des informations qui ont été envoyées par votre application.

Cela présente deux avantages:

  1. Vous n'avez pas à héberger de code sur votre serveur local (sécurité)
  2. Le code peut être sur des serveurs distants (extensibilité) dans différentes langues autres que PHP (portabilité)
La source