php plugins architecture hook

La mejor manera de permitir plugins para una aplicación PHP



architecture hook (8)

Creo que la forma más fácil sería seguir el propio consejo de Jeff y echar un vistazo a los códigos existentes. Intente buscar en Wordpress, Drupal, Joomla y otros CMS bien conocidos basados ​​en PHP para ver cómo se ven y se sienten sus API. De esta manera, incluso puede obtener ideas que no haya pensado previamente para hacer que las cosas sean un poco más desagradables.

Una respuesta más directa sería escribir los archivos generales que "incluirían una vez" en su archivo que proporcionaría la facilidad de uso que necesitarían. Esto se dividiría en categorías y NO se proporcionaría en un archivo MASSIVE "hooks.php". Sin embargo, tenga cuidado, porque lo que termina sucediendo es que los archivos que incluyen terminan teniendo más y más dependencias y funcionalidad. Trate de mantener bajas las dependencias API. IE menos archivos para que incluyan.

Estoy iniciando una nueva aplicación web en PHP y esta vez quiero crear algo que la gente pueda extender usando una interfaz de plugin.

¿Cómo se puede escribir ''ganchos'' en su código para que los complementos puedan adjuntarse a eventos específicos?


El método de enganche y escucha es el más utilizado, pero hay otras cosas que puede hacer. Dependiendo del tamaño de su aplicación, y de quién va a permitir ver el código (esto será un script de FOSS, o algo interno) influirá enormemente en cómo desea permitir los complementos.

kdeloach tiene un buen ejemplo, pero su implementación y función de enganche es un poco insegura. Le pido que brinde más información sobre la naturaleza de la aplicación php que escribe, y cómo ve que se ajustan los complementos.

+1 a kdeloach de mi parte.


Entonces, digamos que no desea el patrón Observer porque requiere que cambie los métodos de clase para manejar la tarea de escuchar y que desee algo genérico. Y digamos que no desea utilizar la herencia extends porque es posible que ya esté heredando en su clase de alguna otra clase. ¿No sería genial tener una forma genérica de hacer que cualquier clase sea conectable sin mucho esfuerzo ? Así es cómo:

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

En la Parte 1, eso es lo que podría incluir con una llamada require_once() en la parte superior de su script PHP. Carga las clases para hacer algo enchufable.

En la Parte 2, ahí es donde cargamos una clase. Tenga en cuenta que no tuve que hacer nada especial para la clase, que es significativamente diferente al patrón Observer.

En la Parte 3, ahí es donde cambiamos nuestra clase para que sea "conectable" (es decir, admite complementos que nos permiten anular los métodos y las propiedades de la clase). Entonces, por ejemplo, si tiene una aplicación web, podría tener un registro de complementos y podría activar los complementos aquí. Observe también la función Dog_bark_beforeEvent() . Si configuro $mixed = ''BLOCK_EVENT'' antes de la declaración de retorno, bloqueará el ladrón del perro y también bloqueará el Dog_bark_afterEvent porque no habría ningún evento.

En la Parte 4, ese es el código de operación normal, pero observe que lo que podría pensar que se ejecutaría no se ejecuta así. Por ejemplo, el perro no anuncia su nombre como ''Fido'', sino ''Coco''. El perro no dice "maullido", sino "guau". Y cuando quieres ver el nombre del perro después, encuentras que es ''Diferente'' en lugar de ''Coco''. Todas esas anulaciones se proporcionaron en la Parte 3.

Entonces, ¿cómo funciona esto? Bueno, eval() (lo que todos dicen que es "malvado") y descartamos que no sea un patrón de Observador. Por lo tanto, la forma en que funciona es la clase vacía y engañosa llamada Pluggable, que no contiene los métodos y propiedades utilizados por la clase Dog. Por lo tanto, desde que eso ocurra, los métodos mágicos se activarán para nosotros Es por eso que en las partes 3 y 4 nos metemos con el objeto derivado de la clase Pluggable, no con la clase Dog. En su lugar, dejamos que la clase de Complementos haga el "toque" del objeto Perro por nosotros. (Si ese es algún tipo de patrón de diseño que no conozco, hágamelo saber).


Este es un enfoque que he usado, es un intento de copiar desde el mecanismo de señales / ranuras Qt, una especie de patrón de observador. Los objetos pueden emitir señales. Cada señal tiene una identificación en el sistema. Está compuesta por el identificador del remitente y el nombre del objeto. Todas las señales pueden vincularse a los receptores, lo que simplemente es "llamable". Se utiliza una clase de bus para pasar las señales a cualquier persona interesada en recibirlas. sucede, usted "envía" una señal. A continuación se muestra una implementación de ejemplo.

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


Hay un buen proyecto llamado Stickleback por Matt Zandstra en Yahoo que maneja gran parte del trabajo para el manejo de complementos en PHP.

Hace cumplir la interfaz de una clase de complemento, es compatible con una interfaz de línea de comandos y no es tan difícil de ponerlo en marcha, especialmente si lee la historia de portada al respecto en la revista PHP Architect .


Me sorprende que la mayoría de las respuestas aquí estén orientadas a los complementos que son locales para la aplicación web, es decir, los complementos que se ejecutan en el servidor web local.

¿Qué pasaría si quisiera que los complementos se ejecuten en un servidor remoto diferente? La mejor manera de hacer esto sería proporcionar un formulario que le permita definir diferentes URL que se llamarán cuando ocurran eventos particulares en su aplicación.

Diferentes eventos enviarían información diferente según el evento que acaba de ocurrir.

De esta manera, simplemente realizará una llamada de CURS a la URL que se le proporcionó a su aplicación (por ejemplo, a través de https) donde los servidores remotos pueden realizar tareas basadas en la información que ha enviado su aplicación.

Esto proporciona dos beneficios:

  1. No tiene que alojar ningún código en su servidor local (seguridad)
  2. El código puede estar en servidores remotos (extensibilidad) en diferentes idiomas además de PHP (portabilidad)

Podrías usar un patrón de observador. Una forma funcional simple de lograr esto:

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

Salida:

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

Notas:

Para este código fuente de ejemplo, debe declarar todos sus complementos antes del código fuente real que desea extender. He incluido un ejemplo de cómo manejar valores simples o múltiples que se pasan al complemento. La parte más difícil de esto es escribir la documentación real que enumera los argumentos que se pasan a cada gancho.

Este es solo un método para lograr un sistema de plugin en PHP. Hay mejores alternativas, le sugiero que consulte la documentación de WordPress para obtener más información.

Lo sentimos, parece que los caracteres de subrayado son reemplazados por las entidades HTML por Markdown? Puedo volver a publicar este código cuando este error se solucione.

Edición: No importa, solo aparece de esa manera cuando estás editando


Un buen consejo es mirar cómo otros proyectos lo han hecho. Muchos solicitan que se instalen complementos y que su "nombre" esté registrado para los servicios (como Wordpress), por lo que tiene "puntos" en su código donde llama a una función que identifica a los oyentes registrados y los ejecuta. Un patrón estándar de diseño OO es el patrón observador , que sería una buena opción para implementar en un sistema PHP verdaderamente orientado a objetos.

Zend Framework hace uso de muchos métodos de enganche y está muy bien diseñado. Ese sería un buen sistema para mirar.