Aquí podría ser tu PUBLICIDAD


La mejor forma de permitir complementos para una aplicación PHP

votos
245

Estoy comenzando una nueva aplicación web en PHP y esta vez quiero crear algo que las personas puedan ampliar mediante el uso de una interfaz de complemento.

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

Publicado el 01/08/2008 a las 13:50
fuente por usuario Wally Lawless
En otros idiomas...        العربية       

8 respuestas

votos
148

Podría usar un patrón Observer. Una forma simple y funcional 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 que se pueda extender. 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, solo aparece de esa manera cuando estás editando

Respondida el 01/08/2008 a las 02:46
fuente por usuario Kevin


Aquí podría ser tu PUBLICIDAD


votos
51

Entonces, digamos que no desea el patrón Observer porque requiere que cambie los métodos de su clase para manejar la tarea de escuchar y desee algo genérico. Y digamos que no desea usar la extendsherencia 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 cualquier clase 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ías incluir con una require_once()llamada en la parte superior de tu script PHP. Carga las clases para hacer algo conectable.

En la Parte 2, 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, es posible que tenga un registro de complemento, y puede activar los complementos aquí. Observe también la Dog_bark_beforeEvent()función. Si configuro $mixed = 'BLOCK_EVENT'antes de la declaración de devolución, bloqueará al perro para que no ladre y también bloquearía 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 tenga en cuenta que lo que podría pensar que se ejecutaría no funciona así. 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()(lo que todos dicen es "malo") y descartemos 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 algún tipo de patrón de diseño que no conozco, por favor avíseme).

Respondida el 01/06/2009 a las 06:59
fuente por usuario Volomike

votos
31

El gancho y el 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 de quién va a permitir que vea el código (esto va a ser un script de FOSS, o algo interno), tendrá una gran influencia sobre cómo quiere permitir los complementos.

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 que los complementos encajan.

+1 a kdeloach de mi parte

Respondida el 01/08/2008 a las 06:23
fuente por usuario w-ll

votos
19

Este es un enfoque que he usado, es un intento de copiar desde el mecanismo de señales / ranuras de Qt, un tipo de patrón de observador. Los objetos pueden emitir señales. Cada señal tiene una ID en el sistema: está compuesta por la identificación del remitente + nombre del objeto. Cada señal puede vincularse a los receptores, que simplemente es "invocable". Usas una clase de bus para pasar las señales a cualquier persona interesada en recibirlas. sucede, "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();

?>
Respondida el 25/09/2008 a las 10:29
fuente por usuario andy.gurin

votos
14

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", lo que proporcionaría 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. IE menos archivos para que incluyan.

Respondida el 01/08/2008 a las 02:44
fuente por usuario helloandre

votos
13

Hay un proyecto estupendo llamado Stickleback por Matt Zandstra en Yahoo que maneja gran parte del trabajo para manejar complementos en PHP.

Impone la interfaz de una clase de complemento, admite una interfaz de línea de comando y no es demasiado difícil de poner en funcionamiento, especialmente si lees la historia de portada en la revista de arquitecto PHP .

Respondida el 17/09/2008 a las 08:00
fuente por usuario julz

votos
10

Un buen consejo es mirar cómo lo han hecho otros proyectos. Muchos piden que se instalen complementos y que su "nombre" se registre para servicios (como WordPress) para que tenga "puntos" en su código donde llama a una función que identifica oyentes registrados y los ejecuta. Un patrón de diseño OO estándar es el patrón de observador , 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 con arquitectura. Ese sería un buen sistema para mirar.

Respondida el 17/09/2008 a las 08:38
fuente por usuario THEMike

votos
7

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, plugins que se ejecutan en el servidor web local.

¿Qué hay de si quería que los plugins se ejecutan en un diferente - remota - servidor? La mejor manera de hacer esto sería proporcionar una forma que le permite definir diferentes direcciones URL que se llamaría cuando suceda un evento en su aplicación.

Diferentes eventos podrían enviar información diferente basado en el supuesto de que acaba de ocurrir.

De esta manera, se acaba de realizar una llamada cURL para la dirección URL que se ha proporcionado a su aplicación (por ejemplo, a través de HTTPS) en servidores remotos pueden realizar tareas basándose en la información que ha sido enviado 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)
Respondida el 22/04/2013 a las 08:41
fuente por usuario timgws