Domanda

Sto avviando una nuova applicazione web in PHP e questa volta voglio creare qualcosa che le persone possano estendere utilizzando un'interfaccia plug-in.

Come si fa a scrivere "hook" nel codice in modo che i plugin possano collegarsi a eventi specifici?

È stato utile?

Soluzione

Potresti usare un modello Observer.Un modo semplice e funzionale per ottenere questo risultato:

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

Produzione:

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

Appunti:

Per questo codice sorgente di esempio, devi dichiarare tutti i tuoi plugin prima del codice sorgente effettivo che desideri sia estendibile.Ho incluso un esempio di come gestire valori singoli o multipli passati al plugin.La parte più difficile è scrivere la documentazione vera e propria che elenca quali argomenti vengono passati a ciascun hook.

Questo è solo un metodo per realizzare un sistema di plugin in PHP.Esistono alternative migliori, ti suggerisco di consultare la documentazione di WordPress per ulteriori informazioni.

Scusa, sembra che i caratteri di sottolineatura siano sostituiti da entità HTML da Markdown?Posso ripubblicare questo codice quando il bug verrà risolto.

Modificare:Non importa, appare così solo durante la modifica

Altri suggerimenti

Quindi diciamo che non vuoi il pattern Observer perché richiede di cambiare i metodi della tua classe per gestire l'attività di ascolto e vuoi qualcosa di generico.E diciamo che non vuoi usare extends ereditarietà perché potresti già ereditare nella tua classe da qualche altra classe.Non sarebbe bello avere un modo generico per farlo qualsiasi classe collegabile senza troppi sforzi?Ecco come:

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

Nella Parte 1, questo è ciò che potresti includere con a require_once() call nella parte superiore dello script PHP.Carica le classi per creare qualcosa di collegabile.

Nella Parte 2, è qui che carichiamo una classe.Nota: non ho dovuto fare nulla di speciale per la classe, che è significativamente diversa dal modello Observer.

Nella Parte 3, è qui che trasformiamo la nostra classe in "collegabile" (ovvero, supporta plugin che ci consentono di sovrascrivere metodi e proprietà della classe).Quindi, ad esempio, se disponi di un'app Web, potresti avere un registro dei plug-in e potresti attivare i plug-in qui.Notare anche il Dog_bark_beforeEvent() funzione.Se imposto $mixed = 'BLOCK_EVENT' prima dell'istruzione return, bloccherà l'abbaiare del cane e bloccherà anche Dog_bark_afterEvent perché non ci sarebbe alcun evento.

Nella Parte 4, questo è il normale codice operativo, ma nota che ciò che potresti pensare venga eseguito non funziona affatto così.Ad esempio, il cane non pronuncia il suo nome come "Fido", ma "Coco".Il cane non dice "miao", ma "bau".E quando poi guardi il nome del cane, scopri che è "Diverso" invece di "Coco".Tutte queste sostituzioni sono state fornite nella Parte 3.

Allora come funziona?Bene, escludiamo eval() (che tutti dicono sia "malvagio") ed escludono che non sia un pattern dell'Observer.Quindi, il modo in cui funziona è la subdola classe vuota chiamata Pluggable, che non contiene i metodi e le proprietà utilizzate dalla classe Dog.Pertanto, dal momento che ciò accade, i metodi magici interverranno per noi.Ecco perché nelle parti 3 e 4 ci occupiamo dell'oggetto derivato dalla classe Pluggable, non della classe Dog stessa.Lasciamo invece che sia la classe Plugin a "toccare" l'oggetto Dog per noi.(Se questo è un tipo di modello di progettazione che non conosco, per favore fatemelo sapere.)

IL gancio E ascoltatore è il metodo più comunemente usato, ma ci sono altre cose che puoi fare.A seconda delle dimensioni della tua app e a chi consentirai di vedere il codice (sarà uno script FOSS o qualcosa interno) influenzerà notevolmente il modo in cui desideri consentire i plug-in.

kdeloach ha un bell'esempio, ma la sua implementazione e la sua funzione di hook sono un po' pericolose.Ti chiederei di fornire maggiori informazioni sulla natura dell'app php che scrivi e su come vedi che si adattano i plugin.

+1 per kdeloach da me.

Ecco un approccio che ho usato, è un tentativo di copiare dal meccanismo dei segnali/slot di Qt, una sorta di pattern Observer.Gli oggetti possono emettere segnali.Ogni segnale ha un ID nel sistema: è composto dal nome ID + oggetto del mittente ogni segnale può essere vincolato ai ricevitori, che è semplicemente un "callabile" che usi una classe di bus per passare i segnali a chiunque sia interessato a riceverli quando qualcosa Succede, "invii" un segnale.Di seguito è riportato un esempio di implementazione

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

?>

Credo che il modo più semplice sarebbe seguire il consiglio di Jeff e dare un'occhiata al codice esistente.Prova a guardare Wordpress, Drupal, Joomla e altri CMS ben noti basati su PHP per vedere come appaiono i loro hook API.In questo modo puoi anche avere idee a cui potresti non aver pensato prima per rendere le cose un po' più robuste.

Una risposta più diretta sarebbe quella di scrivere file generali che "include_once" nel loro file in modo da fornire l'usabilità di cui avrebbero bisogno.Questo verrebbe suddiviso in categorie e NON fornito in un ENORME file "hooks.php".Fai attenzione però, perché ciò che succede è che i file che includono finiscono per avere sempre più dipendenze e la funzionalità migliora.Cerca di mantenere basse le dipendenze API.Cioè meno file da includere.

C'è un bel progetto chiamato Spinarello di Matt Zandstra presso Yahoo che gestisce gran parte del lavoro per la gestione dei plugin in PHP.

Rafforza l'interfaccia di una classe plugin, supporta un'interfaccia a riga di comando e non è troppo difficile da installare e utilizzare, soprattutto se leggi la storia di copertina a riguardo nel Rivista per architetti PHP.

Un buon consiglio è guardare come lo hanno fatto gli altri progetti.Molti richiedono l'installazione di plugin e il loro "nome" registrato per i servizi (come fa WordPress) in modo da avere "punti" nel codice in cui chiamare una funzione che identifica gli ascoltatori registrati e li esegue.Uno schema di progettazione OO standard è il Modello dell'osservatore, che sarebbe una buona opzione da implementare in un sistema PHP veramente orientato agli oggetti.

IL Quadro Zend fa uso di molti metodi di aggancio ed è progettato molto bene.Sarebbe un buon sistema da considerare.

Sono sorpreso che la maggior parte delle risposte qui sembrino orientate ai plug-in locali dell'applicazione Web, ovvero ai plug-in che vengono eseguiti sul server Web locale.

E se volessi che i plugin venissero eseguiti su un server diverso, remoto?Il modo migliore per farlo sarebbe fornire un modulo che consenta di definire diversi URL che verrebbero chiamati quando si verificano eventi particolari nella tua applicazione.

Eventi diversi invierebbero informazioni diverse in base all'evento appena accaduto.

In questo modo, dovresti semplicemente eseguire una chiamata cURL all'URL che è stato fornito alla tua applicazione (ad esempio su https) dove i server remoti possono eseguire attività in base alle informazioni che sono state inviate dalla tua applicazione.

Ciò offre due vantaggi:

  1. Non devi ospitare alcun codice sul tuo server locale (sicurezza)
  2. Il codice può trovarsi su server remoti (estensibilità) in linguaggi diversi da PHP (portabilità)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top