2008-08-01 14 views

Antwort

149

Sie könnten ein Beobachtermuster verwenden. Eine einfache funktionale Art und Weise, dies zu erreichen:

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

Ausgang:

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

Hinweise:

Zu diesem Codebeispiel Quelle, müssen Sie alle Ihre Plugins erklären, bevor die eigentliche Quelle Code, der erweiterbar sein soll. Ich habe ein Beispiel dafür angegeben, wie man mit einzelnen oder mehreren Werten umgehen kann, die an das Plugin übergeben werden. Der schwierigste Teil davon ist das Schreiben der eigentlichen Dokumentation, die auflistet, welche Argumente an jeden Hook übergeben werden.

Dies ist nur eine Methode, um ein Plugin-System in PHP zu erreichen. Es gibt bessere Alternativen, ich schlage vor, Sie lesen die WordPress-Dokumentation für weitere Informationen.

Sorry, es scheint, Unterstreichungszeichen werden von Markdown durch HTML-Entitäten ersetzt? Ich kann diesen Code erneut posten, wenn dieser Fehler behoben wird.

Edit: Nevermind, es scheint nur so, wenn Sie bearbeiten,

+3

Beachten Sie, dass Sie dies für PHP> = 5.0 mithilfe der Observer/Subject-Schnittstellen implementieren können, die in der SPL definiert sind: http://www.php.net/manual/en/class.plobserver.php –

+19

Pedantic note: this is not an Beispiel für das Observer-Muster. Es ist ein Beispiel für das [Mediator Muster] (http://sourcemaking.com/design_patterns/mediator). Wahre Beobachter sind reine Benachrichtigung, es gibt keine Nachrichtenweitergabe oder bedingte Benachrichtigung (noch gibt es einen zentralen Manager zum Steuern von Benachrichtigungen). Es macht nicht die Antwort * falsch *, aber es sollte beachtet werden, dass Menschen Dinge mit dem falschen Namen aufrufen ... – ircmaxell

+0

Beachten Sie, dass wenn Sie mehrere Hooks/Listeners verwenden, sollten Sie nur Zeichenfolgen oder Arrays, nicht beide zurückgeben. Ich habe etwas Ähnliches für Hound CMS implementiert - https://getbutterfly.com/hound/. – Ciprian

13

Ich glaube, der einfachste Weg wäre, Jeffs eigenen Rat zu folgen und sich den vorhandenen Code anzuschauen. Sehen Sie sich Wordpress, Drupal, Joomla und andere bekannte PHP-basierte CMS an, um zu sehen, wie ihre API-Hooks aussehen und sich anfühlen. Auf diese Weise können Sie sogar Ideen bekommen, an die Sie vorher vielleicht noch nicht gedacht haben, um die Dinge ein bisschen rauer zu machen.

Eine direktere Antwort wäre, allgemeine Dateien zu schreiben, die sie "include_once" in ihre Datei aufnehmen würden, die die Benutzerfreundlichkeit bieten würden, die sie benötigen würden. Dies würde in Kategorien aufgeteilt und NICHT in einer MASSIVE "hooks.php" -Datei bereitgestellt werden. Sei aber vorsichtig, denn was passiert, ist, dass die Dateien, die sie enthalten, mehr und mehr Abhängigkeiten haben und die Funktionalität verbessert wird. Versuchen Sie, API-Abhängigkeiten niedrig zu halten. I.E weniger Dateien für sie enthalten.

+0

ich DokuWiki in die Liste der Systeme hinzufügen, würden Sie einen Blick haben. Es hat ein schönes Event-System, das ein reiches Plugin-Ökosystem ermöglicht. – chiborg

31

Der Haken und Zuhörer Methode die am häufigsten verwendete ist, aber es gibt andere Dinge, die Sie tun können. Je nachdem, wie groß deine App ist und wer den Code sehen darf (dies wird ein FOSS-Skript oder etwas im Haus sein), wird stark davon abhängen, wie du Plugins zulassen willst.

kdeloach hat ein schönes Beispiel, aber seine Implementierung und Hook-Funktion ist ein wenig unsicher. Ich würde Sie bitten, mehr Informationen über die Art der PHP-App zu geben, die Sie schreiben, und wie Sie Plugins passen sehen.

+1 zu kdeloach von mir.

12

Es gibt ein nettes Projekt namens Stickleback von Matt Zandstra bei Yahoo, das viel von der Arbeit für die Handhabung von Plugins in PHP übernimmt.

Es erzwingt die Schnittstelle einer Plugin-Klasse, unterstützt eine Kommandozeilen-Schnittstelle und ist nicht zu schwer zu starten - vor allem, wenn Sie die Titelgeschichte darüber in der PHP architect magazine lesen.

9

Guter Rat ist zu schauen, wie andere Projekte es getan haben. Viele fordern Plugins installiert zu haben und ihren "Namen" für Dienste registriert (wie Wordpress tut), so dass Sie "Punkte" in Ihrem Code haben, wo Sie eine Funktion aufrufen, die registrierte Listener identifiziert und sie ausführt. Ein Standard-OO-Design-Muster ist die Observer Pattern, die eine gute Option wäre, um in einem wirklich objektorientierten PHP-System zu implementieren.

Die Zend Framework nutzt viele Hooking-Methoden und ist sehr schön Architektur. Das wäre ein gutes System zum Anschauen.

18

Hier ist ein Ansatz, den ich verwendet habe, es ist ein Versuch, von Qt Signale/Slots-Mechanismus zu kopieren, eine Art Observer-Muster. Objekte können Signale aussenden. Jedes Signal hat eine ID im System - es besteht aus Absender-ID + Objektname Jedes Signal kann an die Empfänger gebunden werden, das ist einfach ein "Callable" Sie verwenden eine Busklasse, um die Signale an alle Interessierten zu übergeben sie Wenn etwas passiert, "senden" Sie ein Signal. Nachfolgend finden und Beispielimplementierung

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

?> 
49

also lasst uns sagen, dass Sie nicht das Beobachter-Muster wollen, weil es erfordert, dass Sie Ihre Klassenmethoden ändern, um die Aufgabe des Zuhörens zu handhaben, und wollen etwas generisch. Nehmen wir an, Sie möchten nicht die Vererbung extends verwenden, da Sie in Ihrer Klasse bereits von einer anderen Klasse erben. Wäre es nicht toll, einen generischen Weg zu haben, irgendeine Klasse steckbar ohne viel Aufwand zu machen? Hier ist, wie:

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

In Teil 1, das ist, was Sie mit einem require_once() Anruf an der Spitze Ihres PHP-Skripts enthalten könnten. Es lädt die Klassen, um etwas Pluggable zu machen.

In Teil 2 laden wir eine Klasse. Hinweis Ich musste der Klasse nichts Besonderes tun, was sich erheblich von dem Observer-Muster unterscheidet.

In Teil 3 verwandeln wir unsere Klasse in "steckbar" (das heißt, unterstützt Plugins, die Klassenmethoden und Eigenschaften überschreiben). Wenn Sie zum Beispiel eine Web-App haben, haben Sie möglicherweise eine Plugin-Registry, und Sie können hier Plugins aktivieren. Beachten Sie auch die Dog_bark_beforeEvent() Funktion. Wenn ich $mixed = 'BLOCK_EVENT' vor der Rückkehranweisung einstelle, blockiert es den Hund vom Bellen und würde auch das Dog_bark_afterEvent blockieren, weil es kein Ereignis geben würde.

In Teil 4 ist das der normale Operationscode, aber beachte, dass das, was du vielleicht denken würdest, nicht so läuft. Zum Beispiel gibt der Hund seinen Namen nicht als "Fido", sondern "Coco" bekannt. Der Hund sagt nicht "Miau", sondern "Wau". Und wenn Sie später den Namen des Hundes sehen wollen, finden Sie, dass es anders ist als "Coco". Alle diese Überschreibungen wurden in Teil 3 bereitgestellt.

Wie funktioniert das? Nun, lassen Sie uns eval() ausschließen (was jeder sagt, ist "böse") und schließen aus, dass es kein Beobachter-Muster ist. Die Art und Weise, wie es funktioniert, ist die hinterhältige leere Klasse namens Pluggable, die die Methoden und Eigenschaften der Dog-Klasse nicht enthält. So, da dies geschieht, werden die magischen Methoden für uns eingreifen. Deshalb mischen wir in den Teilen 3 und 4 mit dem Objekt, das von der Pluggable-Klasse abgeleitet ist, und nicht von der Dog-Klasse selbst.Stattdessen lassen wir die Plugin-Klasse das "Berühren" des Dog-Objekts für uns ausführen. (Wenn das eine Art Designmuster ist, von dem ich nichts weiß - lass es mich wissen.)

+3

Ist das nicht ein Dekorateur? –

+1

Ich [lesen Sie auf Wikipedia darüber] (http://en.wikipedia.org/wiki/Decorator_pattern) und, whoa, du hast Recht! :) – Volomike

6

Ich bin überrascht, dass die meisten Antworten hier auf Plugins ausgerichtet zu sein scheinen, die lokal für die Webanwendung sind, d Plugins, die auf dem lokalen Webserver ausgeführt werden.

Was ist, wenn Sie die Plugins laufen auf einem anderen wollen - Remote - Server? Der beste Weg, dies zu tun, wäre, ein Formular zur Verfügung zu stellen, mit dem Sie verschiedene URLs definieren können, die aufgerufen werden, wenn bestimmte Ereignisse in Ihrer Anwendung auftreten.

Verschiedene Ereignisse senden unterschiedliche Informationen basierend auf dem gerade aufgetretenen Ereignis.

Auf diese Weise würden Sie einfach einen cURL-Aufruf an die URL ausführen, die Ihrer Anwendung zur Verfügung gestellt wurde (z. B. über https), wo Remote-Server Aufgaben basierend auf Informationen ausführen können, die von Ihrer Anwendung gesendet wurden.

Dies bietet zwei Vorteile:

  1. Sie müssen einen Code auf dem lokalen Server nicht Host (Sicherheit)
  2. Der Code auf Remote-Servern (Erweiterbarkeit) in verschiedenen anderen Sprachen dann PHP sein kann (Portabilität)
+8

Dies ist eher eine "Push-API" als ein "Plugin" -System - Sie bieten anderen Diensten die Möglichkeit, Benachrichtigungen über ausgewählte Ereignisse zu erhalten. Unter "Plugins" wird im Allgemeinen verstanden, dass Sie die Anwendung installieren und dann Funktionen hinzufügen können, um ihr Verhalten an Ihre Zwecke anzupassen. Dazu muss das Plugin lokal ausgeführt werden oder zumindest über eine sichere und effiziente 2-Wege-Kommunikation verfügen Informationen * zu * der Anwendung nicht nur * von * es. Die zwei Merkmale sind etwas unterschiedlich, und in vielen Fällen ist ein "Feed" (z. B. RSS, iCal) eine einfache Alternative zu einer Push-API. – IMSoP