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




code seite (8)

Ich beginne eine neue Web-Anwendung in PHP und dieses Mal möchte ich etwas schaffen, das Leute mit einer Plugin-Schnittstelle erweitern können.

Wie schreibt man "Hooks" in ihren Code, damit sich Plugins an bestimmte Ereignisse anhängen können?


Answers

Hier ist ein Ansatz, den ich verwendet habe, es ist ein Versuch, vom Qt-Signal / Slots-Mechanismus zu kopieren, eine Art Beobachtermuster. Objekte können Signale aussenden. Jedes Signal hat eine ID im System - es besteht aus der ID des Senders und dem Objektnamen. Jedes Signal kann an die Empfänger gebunden werden, was einfach "abrufbar" ist. Sie verwenden eine Busklasse, um die Signale an irgendjemanden zu senden, der sie empfangen möchte passiert, Sie "senden" ein Signal. Unten ist und Beispiel Implementierung

    <?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 eigenen Rat zu befolgen und sich den vorhandenen Code anzusehen. 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. IE weniger Dateien für sie enthalten.


Sie könnten ein Beobachtermuster verwenden. Ein einfacher funktioneller 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 Ihre Plugins vor dem tatsächlichen Quellcode deklarieren, 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 durch HTML-Elemente von Markdown ersetzt? Ich kann diesen Code erneut posten, wenn dieser Fehler behoben wird.

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


Ich bin überrascht, dass die meisten Antworten hier auf Plugins ausgerichtet sind, die lokal für die Webanwendung sind, dh Plugins, die auf dem lokalen Webserver laufen.

Was ist, wenn Sie möchten, dass die Plugins auf einem anderen - Remote - Server laufen? 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 keinen Code auf Ihrem lokalen Server hosten (Sicherheit)
  2. Der Code kann auf entfernten Servern (Erweiterbarkeit) in verschiedenen Sprachen außer PHP (Portabilität) sein

Nehmen wir an, Sie möchten das Observer-Muster nicht, da Sie Ihre Klassenmethoden ändern müssen, um die Aufgabe des Zuhörens zu bewältigen, und etwas Generisches wollen. Angenommen, Sie möchten die Vererbung von Erweiterungen nicht verwenden, da Sie möglicherweise bereits von einer anderen Klasse in Ihrer Klasse erben. Wäre es nicht großartig, einen generischen Weg zu haben, um jede Klasse ohne viel 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 mit einem Aufruf von require_once() am Anfang Ihres PHP-Skripts tun. 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 Funktion Dog_bark_beforeEvent() . Wenn ich vor der Rückkehranweisung $mixed = 'BLOCK_EVENT' , wird der Hund daran gehindert, zu bellen und würde auch das Dog_bark_afterEvent blockieren, da kein Event stattfinden würde.

In Teil 4 ist das der normale Operationscode, aber beachten Sie, dass das, was Sie vielleicht denken würden, 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() (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 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.)


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-Entwurfsmuster ist das Observer-Muster , das eine gute Option zur Implementierung in einem wirklich objektorientierten PHP-System wäre.

Das Zend Framework nutzt viele Hooking-Methoden und ist sehr gut aufgebaut. Das wäre ein gutes System zum Anschauen.


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

Es erzwingt die Schnittstelle einer Plug-in-Klasse, unterstützt eine Kommandozeilen-Schnittstelle und ist nicht schwer zu installieren - besonders, wenn Sie die Titelstory dazu im PHP architect Magazin lesen.


Ich habe dies sehr gründlich auf einem ziemlich komplexen, leicht verschachtelten Multi-Hash mit allen Arten von Daten getestet (String, NULL, Integer), und Serialisierung / Deserialisierung endete viel schneller als json_encode / json_decode.

Der einzige Vorteil, den json in meinen Tests hatte, war die kleinere "gepackte" Größe.

Diese sind unter PHP 5.3.3 gemacht, lassen Sie mich wissen, wenn Sie weitere Details wünschen.

Hier sind Testergebnisse dann der Code, um sie zu produzieren. Ich kann die Testdaten nicht liefern, da sie Informationen offen legen, die ich nicht in der Wildnis rauslassen kann.

JSON encoded in 2.23700618744 seconds
PHP serialized in 1.3434419632 seconds
JSON decoded in 4.0405561924 seconds
PHP unserialized in 1.39393305779 seconds

serialized size : 14549
json_encode size : 11520
serialize() was roughly 66.51% faster than json_encode()
unserialize() was roughly 189.87% faster than json_decode()
json_encode() string was roughly 26.29% smaller than serialize()

//  Time json encoding
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    json_encode( $test );
}
$jsonTime = microtime( true ) - $start;
echo "JSON encoded in $jsonTime seconds<br>";

//  Time serialization
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    serialize( $test );
}
$serializeTime = microtime( true ) - $start;
echo "PHP serialized in $serializeTime seconds<br>";

//  Time json decoding
$test2 = json_encode( $test );
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    json_decode( $test2 );
}
$jsonDecodeTime = microtime( true ) - $start;
echo "JSON decoded in $jsonDecodeTime seconds<br>";

//  Time deserialization
$test2 = serialize( $test );
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    unserialize( $test2 );
}
$unserializeTime = microtime( true ) - $start;
echo "PHP unserialized in $unserializeTime seconds<br>";

$jsonSize = strlen(json_encode( $test ));
$phpSize = strlen(serialize( $test ));

echo "<p>serialized size : " . strlen(serialize( $test )) . "<br>";
echo "json_encode size : " . strlen(json_encode( $test )) . "<br></p>";

//  Compare them
if ( $jsonTime < $serializeTime )
{
    echo "json_encode() was roughly " . number_format( ($serializeTime / $jsonTime - 1 ) * 100, 2 ) . "% faster than serialize()";
}
else if ( $serializeTime < $jsonTime )
{
    echo "serialize() was roughly " . number_format( ($jsonTime / $serializeTime - 1 ) * 100, 2 ) . "% faster than json_encode()";
} else {
    echo 'Unpossible!';
}
    echo '<BR>';

//  Compare them
if ( $jsonDecodeTime < $unserializeTime )
{
    echo "json_decode() was roughly " . number_format( ($unserializeTime / $jsonDecodeTime - 1 ) * 100, 2 ) . "% faster than unserialize()";
}
else if ( $unserializeTime < $jsonDecodeTime )
{
    echo "unserialize() was roughly " . number_format( ($jsonDecodeTime / $unserializeTime - 1 ) * 100, 2 ) . "% faster than json_decode()";
} else {
    echo 'Unpossible!';
}
    echo '<BR>';
//  Compare them
if ( $jsonSize < $phpSize )
{
    echo "json_encode() string was roughly " . number_format( ($phpSize / $jsonSize - 1 ) * 100, 2 ) . "% smaller than serialize()";
}
else if ( $phpSize < $jsonSize )
{
    echo "serialize() string was roughly " . number_format( ($jsonSize / $phpSize - 1 ) * 100, 2 ) . "% smaller than json_encode()";
} else {
    echo 'Unpossible!';
}






php plugins architecture hook