2008-08-01 11 views

Respuesta

149

Se podría utilizar un patrón Observer. Una manera simple y funcional para 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 que este código fuente de ejemplo, debe declarar todos sus plugins antes de que la fuente real código que desea que sea extensible. He incluido un ejemplo de cómo manejar valores individuales o múltiples que se pasan al complemento. La parte más difícil de esto es escribir la documentación real que enumera qué argumentos pasan a cada gancho.

Este es solo un método para lograr un sistema de complemento 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 entidades HTML por Markdown? Puedo volver a publicar este código cuando se solucione este error.

Editar: No importa, que sólo aparece de esa manera cuando se está editando

+3

Tenga en cuenta que para PHP> = 5.0 puede implementar esto utilizando las interfaces Observer/Subject definidas en el SPL: http://www.php.net/manual/en/class.splobserver.php –

+19

Nota pedante: esto no es un ejemplo del patrón Observer. Es un ejemplo de ['Mediator Pattern'] (http://sourcemaking.com/design_patterns/mediator). Los verdaderos observadores son puramente notificaciones, no hay mensajes que pasan ni notificaciones condicionales (ni existe un administrador central para controlar las notificaciones). No hace que la respuesta * sea incorrecta *, pero se debe tener en cuenta para evitar que las personas llamen cosas por el nombre incorrecto ... – ircmaxell

+0

Tenga en cuenta que al usar múltiples ganchos/oyentes, solo debe devolver cadenas o matrices, no ambas. He implementado algo similar para Hound CMS - https://getbutterfly.com/hound/. – Ciprian

13

Creo que la manera más fácil sería seguir los consejos de Jeff y echar un vistazo al código existente. Prueba mirar en Wordpress, Drupal, Joomla y otros conocidos CMS basados ​​en PHP para ver cómo se ven y se sienten sus ganchos API. De esta forma, incluso puede obtener ideas en las que quizás no haya pensado antes para hacer que las cosas sean un poco más rubusticas.

Una respuesta más directa sería escribir archivos generales que incluirían en su archivo "include_once" que proporcionarían la usabilidad que necesitarían. Esto se dividiría en categorías y NO se proporcionaría en un archivo MASSIVE "hooks.php". Tenga cuidado, porque lo que termina sucediendo es que los archivos que incluyen terminan teniendo más y más dependencias y la funcionalidad mejora. Intenta mantener bajas las dependencias API. I.E menos archivos para que incluyan.

+0

Agregaría DokuWiki a la lista de sistemas que puede consultar. Tiene un buen sistema de eventos que permite un rico ecosistema de complementos. – chiborg

31

El gancho y oyente método es el más comúnmente utilizado, pero hay otras cosas que puede hacer. Dependiendo del tamaño de su aplicación, y que el va a permitir ver el código (es este va a ser un guión de software libre, o algo en la casa) tendrá gran influencia en la forma en que desea permitir plugins.

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

+1 a kdeloach de mi parte.

12

Hay un proyecto llamado ordenada Stickleback por Matt Zandstra en Yahoo que se encarga de gran parte del trabajo para el manejo de plugins en PHP.

Impone la interfaz de una clase de complemento, admite una interfaz de línea de comandos y no es demasiado difícil de poner en funcionamiento, especialmente si lee la historia de portada al respecto en el PHP architect magazine.

9

Un buen consejo es mirar cómo otros proyectos lo han hecho. Muchos llaman por tener instalados los plugins y su "nombre" registrada por los servicios (como wordpress hace) por lo que tiene "puntos" en su código en el que llama a una función que identifica a los oyentes registrados y los ejecuta. Un patrón de diseño OO estándar es el Observer Pattern, que sería una buena opción para implementar en un sistema PHP orientado a objetos.

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

18

Este es un enfoque que he usado, es un intento de copiar desde el mecanismo de señales Qt/ranuras, un tipo de patrón de observador. Los objetos pueden emitir señales. Cada señal tiene un ID en el sistema - que está compuesto por el nombre de ID de objeto + del remitente Cada señal se puede enganchan a los receptores, que simplemente es un "exigible" se utiliza una clase de autobús para pasar las señales a cualquiera interesado en recibir ellos Cuando sucede algo, "envía" una señal. A continuación se muestra y ejemplo de implementación

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

?> 
49

Así que digamos que usted no quiere que el patrón de observador, ya que requiere que cambie sus métodos de clase para manejar la tarea de escuchar, y quieren algo genérico. Y digamos que usted no desea utilizar extends herencia porque usted ya puede estar heredando en su clase de alguna otra clase. ¿No sería bueno tener una forma genérica para hacer cualquier clase enchufable sin mucho esfuerzo? Así es como:

<?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 se podría incluir en una llamada require_once() en la parte superior de su script PHP. Carga las clases para hacer algo conectable.

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 en "conectable" (es decir, admite complementos que nos permiten anular las propiedades y los métodos de clase). Entonces, por ejemplo, si tiene una aplicación web, es posible que tenga un registro de complemento, y puede activar los complementos aquí. Observe también la función Dog_bark_beforeEvent(). Si fijo $mixed = 'BLOCK_EVENT' antes de la instrucción de retorno, bloqueará el perro ladrando y que también bloquean la Dog_bark_afterEvent porque no habría ningún evento.

En la Parte 4, ese es el código de operación normal, pero tenga en cuenta que lo que podría pensar que se ejecutaría no funciona de esa manera. Por ejemplo, el perro no anuncia su nombre como 'Fido', sino 'Coco'. El perro no dice 'miau', sino 'Guau'. Y cuando quieras mirar el nombre del perro después, encontrarás que es 'Diferente' en lugar de 'Coco'. Todas esas anulaciones se proporcionaron en la Parte 3.

Entonces, ¿cómo funciona esto? Bueno, descartemos eval() (que todos dicen que es "malo") y descarte que no sea un patrón de Observador. Por lo tanto, la forma en que funciona es la clase vacía furtiva llamada Pluggable, que no contiene los métodos y las propiedades utilizadas por la clase Dog. Por lo tanto, dado que eso ocurre, los métodos mágicos se comprometerán con nosotros. Es por eso que en las partes 3 y 4 nos metimos con el objeto derivado de la clase Pluggable, no de la clase Dog.En su lugar, dejamos que la clase Plugin haga el "toque" en el objeto Dog para nosotros. (Si eso es una especie de patrón de diseño que no sé acerca de - por favor hágamelo saber.)

+3

¿No es esto un decorador? –

+1

I [leer en Wikipedia sobre esto] (http://en.wikipedia.org/wiki/Decorator_pattern) y, ¡vaya, tienes razón! :) – Volomike

6

me sorprende que la mayoría de las respuestas aquí parecen estar orientado acerca de los plugins que son locales a la aplicación web, es decir, , complementos que se ejecutan en el servidor web local.

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

Los diferentes eventos enviarían información diferente en función del evento que acaba de ocurrir.

De esta manera, solo realizaría una llamada cURL a la URL que se ha proporcionado a su aplicación (por ejemplo, sobre https) donde los servidores remotos pueden realizar tareas basadas en información que ha sido enviada por su aplicación.

Esto proporciona dos ventajas:

  1. Usted no tiene que acoger cualquier código en el servidor local (seguridad)
  2. El código puede ser en servidores remotos (extensibilidad) en diferentes idiomas aparte de PHP (portabilidad)
+8

Esto es más un "API de inserción" que un sistema de "complementos": usted está proporcionando una forma para que otros servicios reciban notificaciones de eventos seleccionados. Lo que generalmente se entiende por "complementos" es que puede instalar la aplicación y luego agregar funcionalidad para personalizar su comportamiento para sus propósitos, lo que requiere que el complemento se ejecute localmente o al menos tenga una comunicación bidireccional segura y eficiente para proporcionar información * a * la aplicación no solo la toma * de * it. Las dos características son algo distintas, y en muchos casos un "feed" (por ejemplo, RSS, iCal) es una alternativa simple a una API de inserción. – IMSoP

Cuestiones relacionadas