wp_title - Beste Möglichkeit, Plugins für eine PHP-Anwendung zuzulassen




wordpress title tag (6)

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

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


Die Hook- und Listener- Methode wird am häufigsten verwendet, Sie können jedoch auch andere Aktionen ausführen. Abhängig von der Größe Ihrer App und davon, wem Sie das Anzeigen des Codes erlauben (ist dies ein FOSS-Skript oder etwas Eigenes), 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 Sie Plugins finden.

+1 an kdeloach von mir.


Es gibt ein nettes Projekt namens Stickleback von Matt Zandstra bei Yahoo, das 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 - besonders wenn Sie die Titelgeschichte darüber im PHP Architect-Magazin lesen.


Hier ist ein Ansatz, den ich verwendet habe: Es ist ein Versuch, aus dem Qt-Signal- / Schlitzmechanismus eine Art Beobachtermuster zu kopieren. Objekte können Signale aussenden. Jedes Signal hat eine ID im System - es setzt sich aus der ID des Absenders und dem Objektnamen zusammen. Jedes Signal kann an die Empfänger gebunden werden. Dies ist einfach "abrufbar". Sie verwenden eine Busklasse, um die Signale an alle weiterzuleiten, die daran interessiert sind, sie zu empfangen passiert, "senden" Sie ein Signal. Unten finden Sie ein Implementierungsbeispiel

    <?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 bin überrascht, dass sich die meisten Antworten hier auf Plugins beziehen, die lokal für die Webanwendung sind, dh Plugins, die auf dem lokalen Webserver ausgeführt werden.

Wie wäre es, wenn die Plugins auf einem anderen - entfernten - Server laufen sollen? Am besten stellen Sie dazu ein Formular bereit, 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 aus, 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 auf Remote-Servern (Erweiterbarkeit) in anderen Sprachen als PHP (Portabilität) vorliegen.

Nehmen wir also an, Sie möchten das Observer-Muster nicht, weil Sie dafür Ihre Klassenmethoden ändern müssen, um die Aufgabe des Zuhörens zu bewältigen, und etwas Allgemeines wollen. Nehmen wir an, Sie möchten die extends Vererbung nicht verwenden, da Sie möglicherweise bereits von einer anderen Klasse in Ihrer Klasse erben. Wäre es nicht großartig, eine generische Methode zu haben, um eine Klasse ohne großen Aufwand steckbar 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 können Sie dies in einen require_once() oben in Ihrem PHP-Skript einfügen. Es lädt die Klassen, um etwas Steckbares zu machen.

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

In Teil 3 stellen wir unsere Klasse auf "pluggable" um (dh unterstützen Plugins, mit denen wir Klassenmethoden und -eigenschaften überschreiben können). Wenn Sie beispielsweise eine Web-App haben, verfügen Sie möglicherweise über eine Plug-in-Registrierung, und Sie können hier Plug-ins aktivieren. Beachten Sie auch die Dog_bark_beforeEvent() Funktion. Wenn ich vor der return-Anweisung $mixed = 'BLOCK_EVENT' , wird das Bellen des Hundes und das Bellen des Dog_bark_afterEvent blockiert, da es kein Ereignis geben würde.

In Teil 4 ist das der normale Operationscode, aber beachten Sie, dass das, was Sie vielleicht denken würden, überhaupt nicht so abläuft. Zum Beispiel sagt der Hund nicht "Fido" an, sondern "Coco". Der Hund sagt nicht 'meow', sondern 'Woof'. Und wenn Sie sich den Namen des Hundes später ansehen möchten, stellen Sie fest, dass er „anders“ ist als „Coco“. Alle diese Überschreibungen wurden in Teil 3 bereitgestellt.

Wie funktioniert das? Nun, lasst uns eval() (was jeder für "böse" hält) und ausschließen, dass es sich nicht um ein Observer-Muster handelt. So funktioniert es also mit der hinterhältigen leeren Klasse Pluggable, die nicht die Methoden und Eigenschaften enthält, die von der Dog-Klasse verwendet werden. Da dies geschieht, werden sich die magischen Methoden für uns einsetzen. Aus diesem Grund spielen wir in Teil 3 und 4 mit dem Objekt, das aus der Pluggable-Klasse stammt, und nicht mit der Dog-Klasse. Stattdessen lassen wir die Plugin-Klasse das "Berühren" des Dog-Objekts für uns erledigen. (Wenn es sich um ein Designmuster handelt, von dem ich nichts weiß, lassen Sie es mich bitte wissen.)


Sie könnten ein Observer-Muster verwenden. Eine einfache Funktionsweise, 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 Ihre Plugins vor dem eigentlichen Quellcode deklarieren, den Sie erweitern möchten. Ich habe ein Beispiel beigefügt, wie einzelne oder mehrere Werte behandelt werden, die an das Plugin übergeben werden. Das Schwierigste dabei ist, die eigentliche Dokumentation zu schreiben, in der aufgelistet ist, welche Argumente an die einzelnen Hooks übergeben werden.

Dies ist nur eine Methode, um ein Plugin-System in PHP zu erstellen. Es gibt bessere Alternativen. Weitere Informationen finden Sie in der WordPress-Dokumentation.

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

Bearbeiten: Vergiss nicht, es erscheint nur so, wenn du es bearbeitest





hook