Frage

Ich starte eine neue Webanwendung in PHP und dieses Mal möchte ich etwas erstellen, das die Leute mithilfe einer Plugin-Schnittstelle erweitern können.

Wie schreibt man „Hooks“ in seinen Code, damit Plugins an bestimmte Ereignisse angehängt werden können?

War es hilfreich?

Lösung

Sie könnten ein Observer-Muster verwenden.Eine einfache funktionale Möglichkeit, 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;
?>

Ausgabe:

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

Anmerkungen:

Für diesen Beispielquellcode müssen Sie alle Ihre Plugins vor dem eigentlichen Quellcode deklarieren, der erweiterbar sein soll.Ich habe ein Beispiel für den Umgang mit einzelnen oder mehreren Werten beigefügt, die an das Plugin übergeben werden.Der schwierigste Teil davon besteht darin, die eigentliche Dokumentation zu schreiben, in der aufgeführt ist, welche Argumente an die einzelnen Hooks übergeben werden.

Dies ist nur eine Möglichkeit, ein Plugin-System in PHP zu erstellen.Es gibt bessere Alternativen. Ich empfehle Ihnen, sich für weitere Informationen die WordPress-Dokumentation anzusehen.

Tut mir leid, es sieht so aus, als würden Unterstriche durch Markdown durch HTML-Entitäten ersetzt?Ich kann diesen Code erneut veröffentlichen, wenn dieser Fehler behoben ist.

Bearbeiten:Aber egal, es sieht nur so aus, wenn Sie es bearbeiten

Andere Tipps

Nehmen wir also an, Sie möchten das Observer-Muster nicht, weil es erfordert, dass Sie Ihre Klassenmethoden ändern, um die Aufgabe des Zuhörens zu bewältigen, und Sie möchten etwas Generisches.Und nehmen wir an, Sie möchten es nicht verwenden extends Vererbung, da Sie in Ihrer Klasse möglicherweise bereits von einer anderen Klasse erben.Wäre es nicht toll, eine generische Art der Herstellung zu haben? jede Klasse ohne großen Aufwand steckbar?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 könnten Sie das mit einschließen require_once() rufen Sie oben in Ihrem PHP-Skript auf.Es lädt die Klassen, um etwas steckbares zu machen.

In Teil 2 laden wir dort eine Klasse.Beachten Sie, dass ich an der Klasse, die sich deutlich vom Observer-Muster unterscheidet, nichts Besonderes tun musste.

In Teil 3 stellen wir unsere Klasse so um, dass sie „pluggable“ ist (d. h. Plugins unterstützt, mit denen wir Klassenmethoden und -eigenschaften überschreiben können).Wenn Sie beispielsweise eine Web-App haben, verfügen Sie möglicherweise über eine Plugin-Registrierung und können Plugins hier aktivieren.Beachten Sie auch die Dog_bark_beforeEvent() Funktion.Wenn ich setze $mixed = 'BLOCK_EVENT' vor der return-Anweisung verhindert, dass der Hund bellt, und blockiert auch Dog_bark_afterEvent, da es kein Ereignis gäbe.

In Teil 4 ist das der normale Betriebscode, aber beachten Sie, dass das, was Sie vielleicht denken, dass es ausgeführt werden würde, überhaupt nicht so ausgeführt wird.Beispielsweise nennt der Hund seinen Namen nicht „Fido“, sondern „Coco“.Der Hund sagt nicht „Miau“, sondern „Wuff“.Und wenn man sich hinterher den Namen des Hundes ansehen will, stellt man fest, dass es sich um „Anders“ und nicht um „Coco“ handelt.Alle diese Außerkraftsetzungen wurden in Teil 3 bereitgestellt.

Wie funktioniert das?Nun, schließen wir es aus eval() (was jeder für „böse“ hält) und schließen Sie aus, dass es sich nicht um ein Observer-Muster handelt.Die Art und Weise, wie es funktioniert, ist die hinterhältige leere Klasse namens Pluggable, die nicht die von der Dog-Klasse verwendeten Methoden und Eigenschaften enthält.Sobald dies geschieht, werden die magischen Methoden für uns wirksam.Aus diesem Grund beschäftigen wir uns in den Teilen 3 und 4 mit dem von der Pluggable-Klasse abgeleiteten Objekt und nicht mit der Dog-Klasse selbst.Stattdessen überlassen wir die „Berührung“ des Dog-Objekts der Plugin-Klasse.(Wenn es sich um ein Designmuster handelt, das ich nicht kenne, lassen Sie es mich bitte wissen.)

Der Haken Und Hörer Die Methode wird am häufigsten verwendet, es gibt jedoch noch andere Dinge, die Sie tun können.Abhängig von der Größe Ihrer App und davon, wer den Code sehen darf (wird es sich um ein FOSS-Skript oder etwas Eigenes handeln), hat dies großen Einfluss darauf, wie Sie Plugins zulassen möchten.

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

+1 für kdeloach von mir.

Hier ist ein Ansatz, den ich verwendet habe. Es ist ein Versuch, den Qt-Signals/Slots-Mechanismus zu kopieren, eine Art Observer-Muster.Objekte können Signale aussenden.Jedes Signal enthält eine ID im System - es ist durch den ID + -Objektnamen des Absenders komponiert. Jedes Signal kann für die Empfänger gebeten werden. Dies ist einfach ein "aufrufbar". Sie verwenden eine Busklasse, um die Signale an jemanden weiterzugeben passiert, Sie "senden" ein Signal.Nachfolgend finden Sie eine 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();

?>

Ich glaube, der einfachste Weg wäre, Jeffs eigenem Rat zu folgen und sich den vorhandenen Code anzusehen.Schauen 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 auf Ideen kommen, an die Sie vorher vielleicht noch nicht gedacht haben, um die Dinge ein wenig robuster zu gestalten.

Eine direktere Antwort wäre, allgemeine Dateien zu schreiben, die sie „include_once“ in ihre Datei aufnehmen würden, um die Benutzerfreundlichkeit zu gewährleisten, die sie benötigen würden.Dies würde in Kategorien unterteilt und NICHT in einer RIESIGEN „hooks.php“-Datei bereitgestellt.Seien Sie jedoch vorsichtig, denn was letztendlich passiert, ist, dass die darin enthaltenen Dateien immer mehr Abhängigkeiten aufweisen und die Funktionalität verbessert wird.Versuchen Sie, die API-Abhängigkeiten gering zu halten.D.h. sie müssen weniger Dateien einbinden.

Es gibt ein nettes Projekt namens Stichling von Matt Zandstra bei Yahoo, das einen Großteil der Arbeit für die Handhabung von Plugins in PHP übernimmt.

Es erzwingt die Schnittstelle einer Plugin-Klasse, unterstützt eine Befehlszeilenschnittstelle und ist nicht allzu schwer einzurichten und zum Laufen zu bringen – vor allem, wenn Sie die Titelgeschichte darüber im lesen PHP-Architektenmagazin.

Ein guter Rat ist, sich anzusehen, wie andere Projekte es gemacht haben.Viele fordern die Installation von Plugins und die Registrierung ihres „Namens“ für Dienste (wie es bei WordPress der Fall ist), sodass Sie in Ihrem Code „Punkte“ haben, an denen Sie eine Funktion aufrufen, die registrierte Listener identifiziert und ausführt.Ein Standard-OO-Designmuster ist das Beobachtermuster, was eine gute Option für die Implementierung in einem wirklich objektorientierten PHP-System wäre.

Der Zend Framework nutzt viele Hooking-Methoden und ist sehr schön aufgebaut.Das wäre ein gutes System, das man sich ansehen könnte.

Ich bin überrascht, dass sich die meisten Antworten hier auf Plugins zu beziehen scheinen, die lokal für die Webanwendung vorhanden sind, also Plugins, die auf dem lokalen Webserver ausgeführt werden.

Was wäre, wenn Sie möchten, dass die Plugins auf einem anderen – Remote-Server – ausgeführt werden?Der beste Weg hierfür wäre die Bereitstellung eines Formulars, mit dem Sie verschiedene URLs definieren können, die aufgerufen werden, wenn bestimmte Ereignisse in Ihrer Anwendung auftreten.

Verschiedene Ereignisse würden je nach gerade aufgetretenem Ereignis unterschiedliche Informationen senden.

Auf diese Weise führen Sie einfach einen cURL-Aufruf an die URL durch, die Ihrer Anwendung bereitgestellt wurde (z. B. über https), wo Remote-Server Aufgaben basierend auf den von Ihrer Anwendung gesendeten Informationen ausführen können.

Dies bietet zwei Vorteile:

  1. Sie müssen keinen Code auf Ihrem lokalen Server hosten (Sicherheit)
  2. Der Code kann auf Remote-Servern (Erweiterbarkeit) in anderen Sprachen als PHP (Portabilität) vorliegen.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top