Der beste Weg, um Plugins für eine PHP-Anwendung zuzulassen

Translate

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

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

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

Alle Antworten

Translate

Sie können ein Observer-Muster verwenden. Ein einfacher funktionaler Weg, um 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 Plugins vor dem eigentlichen Quellcode deklarieren, der erweiterbar sein soll. Ich habe ein Beispiel beigefügt, wie einzelne oder mehrere Werte behandelt werden, die an das Plugin übergeben werden. Der schwierigste Teil davon ist das Schreiben der eigentlichen Dokumentation, in der aufgeführt ist, welche Argumente an jeden Hook übergeben werden.

Dies ist nur eine Methode, um ein Plugin-System in PHP zu erstellen. Es gibt bessere Alternativen. Ich empfehle Ihnen, die WordPress-Dokumentation zu lesen, um weitere Informationen zu erhalten.

Entschuldigung, es scheint, dass Unterstriche durch Markdown durch HTML-Entitäten ersetzt werden. Ich kann diesen Code erneut veröffentlichen, wenn dieser Fehler behoben ist.

Bearbeiten: Egal, es wird nur so angezeigt, wenn Sie bearbeiten

Quelle
Translate

Nehmen wir also an, Sie möchten das Observer-Muster nicht, da Sie dazu Ihre Klassenmethoden ändern müssen, um die Aufgabe des Abhörens zu bewältigen, und etwas Allgemeines wünschen. Und nehmen wir an, Sie möchten nicht verwendenextendsVererbung, da Sie möglicherweise bereits in Ihrer Klasse von einer anderen Klasse erben. Wäre es nicht großartig, eine generische Methode 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 dies mit a einschließenrequire_once()Rufen Sie oben in Ihrem PHP-Skript auf. Es lädt die Klassen, um etwas steckbar zu machen.

In Teil 2 laden wir dort eine Klasse. Hinweis: Ich musste nichts Besonderes für die Klasse tun, was sich erheblich vom Observer-Muster unterscheidet.

In Teil 3 schalten wir unsere Klasse in "steckbar" um (dh unterstützt Plugins, 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 hier Plugins aktivieren. Beachten Sie auch dieDog_bark_beforeEvent()Funktion. Wenn ich setze$mixed = 'BLOCK_EVENT'vor der return-Anweisung blockiert es das Bellen des Hundes und blockiert auch das Dog_bark_afterEvent, da es kein Ereignis geben würde.

In Teil 4 ist dies der normale Betriebscode. Beachten Sie jedoch, dass das, was Sie vielleicht für möglich halten, überhaupt nicht so ausgeführt wird. Zum Beispiel gibt der Hund seinen Namen nicht als "Fido" bekannt, sondern als "Coco". Der Hund sagt nicht "Miau", sondern "Woof". Und wenn Sie sich später den Namen des Hundes ansehen möchten, finden Sie, dass er "anders" ist als "Coco". Alle diese Überschreibungen wurden in Teil 3 bereitgestellt.

Wie funktioniert das? Nun, lassen Sie uns ausschließeneval()(was jeder sagt, ist "böse") und schließen aus, dass es kein Beobachtermuster ist. 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. Da dies geschieht, werden sich die magischen Methoden für uns engagieren. Deshalb spielen wir in Teil 3 und 4 mit dem Objekt, das von der Pluggable-Klasse abgeleitet wurde, und nicht mit der Dog-Klasse selbst. Stattdessen lassen wir die Plugin-Klasse das Dog-Objekt für uns "berühren". (Wenn das eine Art Designmuster ist, von dem ich nichts weiß - lassen Sie es mich bitte wissen.)

Quelle
Translate

DasHakenundHörerMethode ist die am häufigsten verwendete, aber es gibt andere Dinge, die Sie tun können. Abhängig von der Größe Ihrer App und davon, wem Sie das Anzeigen des Codes erlauben (wird dies ein FOSS-Skript oder etwas im Haus sein), wird dies einen großen Einfluss darauf haben, 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, mehr Informationen über die Art der PHP-App zu geben, die Sie schreiben, und darüber, wie Plugins passen.

+1 bis kdeloach von mir.

Quelle
Translate

Hier ist ein Ansatz, den ich verwendet habe: Es ist ein Versuch, aus dem Qt-Signal- / Slot-Mechanismus eine Art Beobachtermuster zu kopieren. Objekte können Signale aussenden. Jedes Signal hat eine ID im System - es besteht aus der ID des Absenders + dem Objektnamen. Jedes Signal kann an die Empfänger gebunden werden, was einfach "aufrufbar" ist. Sie verwenden eine Busklasse, um die Signale an alle weiterzuleiten, die daran interessiert sind, sie zu empfangen, wenn etwas passiert, "senden" Sie ein Signal. Unten 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();

?>
Quelle
Translate

Ich glaube, der einfachste Weg wäre, Jeffs eigenen Ratschlägen 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 Ideen erhalten, an die Sie vorher vielleicht noch nicht gedacht haben, um die Dinge ein wenig robuster zu machen.

Eine direktere Antwort wäre, allgemeine Dateien zu schreiben, die sie "include_once" in ihre Datei aufnehmen würden, um die Benutzerfreundlichkeit zu bieten, die sie benötigen würden. Dies würde in Kategorien unterteilt und NICHT in einer MASSIVEN "hooks.php" -Datei bereitgestellt. Seien Sie jedoch vorsichtig, denn was am Ende passiert, ist, dass Dateien, die sie enthalten, immer mehr Abhängigkeiten und Funktionen aufweisen. Versuchen Sie, die API-Abhängigkeiten gering zu halten. IE weniger Dateien für sie enthalten.

Quelle
Translate

Es gibt ein ordentliches Projekt namensStichlingvon Matt Zandstra bei Yahoo, der einen Großteil der Arbeit für den Umgang mit Plugins in PHP erledigt.

Es erzwingt die Schnittstelle einer Plugin-Klasse, unterstützt eine Befehlszeilenschnittstelle und ist nicht allzu schwer in Betrieb zu nehmen - insbesondere, wenn Sie die Titelgeschichte darüber in der lesenPHP Architektenmagazin.

Quelle
Translate

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

DasZend Frameworknutzt viele Hakenmethoden und ist sehr schön aufgebaut. Das wäre ein gutes System.

Quelle
Translate

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

Was ist, wenn Sie möchten, dass die Plugins auf einem anderen Remote-Server ausgeführt werden? Der beste Weg, dies zu tun, besteht darin, ein Formular bereitzustellen, mit dem Sie verschiedene URLs definieren können, die aufgerufen werden, wenn bestimmte Ereignisse in Ihrer Anwendung auftreten.

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

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

Dies bietet zwei Vorteile:

  1. Sie müssen keinen Code auf Ihrem lokalen Server hosten (Sicherheit)
  2. Der Code kann sich auf Remote-Servern (Erweiterbarkeit) in anderen Sprachen als PHP (Portabilität) befinden.
Quelle