293 votes

La meilleure façon de permettre à des plugins pour une application en PHP

Je suis en train de créer une nouvelle application web en PHP et cette fois, je veux créer quelque chose que les gens peuvent s'étendre à l'aide d'un plugin d'interface. Comment fait-on pour écrire "crochets" dans leur code, de sorte que les plugins peuvent s'attacher à des événements spécifiques?

167voto

Kevin Points 6567

Vous pouvez utiliser un modèle Observateur. Une simple manière fonctionnelle à accomplir ce:

<?php

Sortie:


Notes:

Pour cet exemple de code source, vous devez déclarer tous vos plugins avant le code source que vous voulez être extensible. J'ai inclus un exemple de la façon de traiter seul ou plusieurs valeurs d'être passé au plugin. La partie la plus difficile de ce d'écrire la documentation disponible qui répertorie les arguments se passait à chaque crochet.

Ce n'est qu'un moyen de réaliser un système de plugin en PHP. Il existe de meilleures alternatives, je vous suggère de vérifier les WordPress Documentation pour plus d'informations.

Désolé, il semble que les caractères de soulignement sont remplacés par les entités HTML en Markdown? Je peux re-poster ce code lorsque ce bug est corrigé.

Edit: Nevermind, il n'apparaît que lorsque vous modifiez

61voto

Volomike Points 7083

Donc, disons que vous ne voulez pas le pattern observer, car il vous demande de changer vos méthodes de la classe pour gérer la tâche de l'écoute, et que vous voulez quelque chose de générique. Et disons que vous ne souhaitez pas l'utiliser s'étend de l'héritage parce que vous avez peut-être déjà hériter de votre classe à partir d'une autre classe. Ne serait-il pas génial d'avoir un générique façon de faire toute la classe enfichables 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 pouvez inclure dans un require_once() en haut de votre script PHP. Il charge les classes pour faire quelque chose enfichable.

Dans la Partie 2, où nous en charge une classe. Remarque je n'avais pas à faire quelque chose de spécial à la classe, ce qui est significativement différente de celle du modèle Observateur.

Dans la Partie 3, c'est là que nous allons passer notre classe autour de en étant "pluggable" (qui est, prend en charge les plugins qui permettent de remplacer les méthodes de la classe et de propriétés). Ainsi, par exemple, si vous avez une application web, vous pourriez avoir un plugin de registre, et vous pourriez activer les plugins ici. Remarquez aussi le "Dog_bark_beforeEvent" de la fonction. Si j'ai mis $mixed = 'BLOCK_EVENT" avant l'instruction de retour, il va bloquer le chien d'aboyer et serait également bloquer le Dog_bark_afterEvent parce qu'il n'y aurait pas de manifestation.

Dans la Partie 4, qui est le fonctionnement normal de code, mais notez que ce que vous pourriez penser irait ne fonctionne pas du tout comme ça. Par exemple, le chien n'a pas l'annoncer le nom de la "Fido", mais "Coco". Le chien n'a pas dit "miaou", mais "Woof'. Et lorsque vous voulez regarder le nom du chien par la suite, vous trouvez qu'il est "Différent" au lieu de "Coco". Tous ces substitutions ont été fournis dans la Partie 3.

Alors, comment cela fonctionne? Eh bien, nous allons exclure eval() (tout le monde dit qui est "mal") et de la règle qu'il n'est pas un modèle Observateur. Donc, la façon dont il fonctionne, c'est le sournois classe vide appelé Enfichable, qui ne contient pas les méthodes et les propriétés utilisées par la classe Chien. Ainsi, dès que cela se produit, les méthodes magiques vont s'engager pour nous. C'est pourquoi, dans les parties 3 et 4, nous avons jouer avec les objets dérivés de la Enfichable de la classe, pas le Chien de la classe elle-même. Au lieu de cela, nous laissons la classe de Plugin ne le "toucher" sur le Chien objet pour nous. (Si c'est une sorte de modèle de conception, je ne sais pas, s'il vous plaît laissez-moi savoir.)

35voto

w-ll Points 1515

Le crochet et l' auditeur de la méthode la plus couramment utilisée, mais il y a d'autres choses que vous pouvez faire. Selon la taille de votre application, et qui vos permettra de voir le code (est-ce que ça va être un FOSS script, ou quelque chose dans la maison) va influencer fortement la façon dont vous voulez autoriser les plug-ins.

kdeloach a un bel exemple, mais sa mise en œuvre et de la fonction de raccordement est un peu dangereux. Je voudrais vous demander de donner plus d'informations de la nature de php app votre écriture, Et la façon dont vous voyez les plugins d'adaptation.

+1 pour kdeloach de moi.

26voto

andy.gurin Points 1516

Ici est une approche que j'ai utilisée, c'est une tentative de copie de Qt signaux/slots mécanisme, une sorte de pattern observer. Les objets peuvent émettre des signaux. Chaque signal a un ID dans le système - il est composé par l'expéditeur id + nom de l'objet Chaque signal peut être appliqué à des récepteurs, ce qui est simplement un "callable" Vous utilisez un bus de la classe pour transmettre les signaux à toute personne intéressée de recevoir Lorsque quelque chose arrive, vous "envoyer" un signal. Ci-dessous est et 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();

?>

20voto

helloandre Points 5784

Je crois que le plus simple serait de suivre Jeff propres conseils et un coup d'oeil dans le code existant. Essayez de regarder Wordpress, Drupal, Joomla et d'autres bien connus en PHP CMS pour voir comment leur API crochets regarder et se sentir. De cette façon, vous pouvez même obtenir des idées que vous pourriez avoir pas pensé auparavant pour rendre les choses un peu plus rubust.

Plus de réponse directe, serait d'écrire des fichiers généraux qu'ils seraient "include_once" dans leur dossier de fournir la facilité d'utilisation, ils auraient besoin. Ce serait divisé en catégories et ne sont PAS fournis dans un MASSIF "hooks.php" fichier. Attention tout de même, car il arrive que les fichiers qu'ils comprennent finissent par avoir plus et plusieurs dépendances et la fonctionnalité améliore. Essayez de garder l'API dépendances faible. I. E moins de fichiers pour eux d'inclure.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X