Pregunta

Estoy iniciando una nueva aplicación web en PHP y esta vez quiero crear algo que la gente pueda ampliar mediante el uso de una interfaz de complemento.

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

¿Fue útil?

Solución

Podrías usar un patrón de observador.Una forma funcional sencilla 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;
?>

Producción:

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 sea ampliable.He incluido un ejemplo de cómo manejar valores únicos 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 se pasan a cada gancho.

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

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

Editar:No importa, solo aparece así cuando estás editando.

Otros consejos

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 desea algo genérico.Y digamos que no quieres usar extends herencia 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 un require_once() llame 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 en 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, admita complementos que nos permitan anular los métodos y propiedades de la clase).Entonces, por ejemplo, si tiene una aplicación web, es posible que tenga un registro de complementos y pueda activar los complementos aquí.Observe también el Dog_bark_beforeEvent() función.si lo configuro $mixed = 'BLOCK_EVENT' antes de la declaración de devolución, bloqueará el ladrido del perro y también bloqueará 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 lo hace en absoluto.Por ejemplo, el perro no anuncia su nombre como 'Fido', sino como 'Coco'.El perro no dice 'miau', sino 'Guau'.Y cuando luego quieres ver el nombre del perro, descubres 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 todo el mundo dice que es "malo") y descartar que no sea un patrón de Observador.Entonces, la forma en que funciona es la astuta clase vacía llamada Pluggable, que no contiene los métodos y propiedades utilizados por la clase Dog.Por lo tanto, cuando eso ocurra, los métodos mágicos actuarán por 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 sí.En su lugar, dejamos que la clase Plugin "toque" el objeto Perro por nosotros.(Si se trata de algún tipo de patrón de diseño que no conozco, hágamelo saber).

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

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

+1 para kdeloach de mi parte.

Aquí hay un enfoque que he usado, es un intento de copiar del mecanismo de señales/ranuras de 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 nombre de ID + objeto del remitente, cada señal se puede vincular a los receptores, que simplemente es un "llamable", usa una clase de bus para pasar las señales a cualquiera interesada en recibirlos cuando algo sucede, usted "envía" una señal.A continuación se muestra un 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();

?>

Creo que la forma más sencilla sería seguir el propio consejo de Jeff y echar un vistazo al código existente.Intente mirar Wordpress, Drupal, Joomla y otros CMS conocidos basados ​​en PHP para ver cómo se ven y funcionan sus enlaces API.De esta manera, incluso puedes obtener ideas en las que quizás no hayas pensado antes para hacer las cosas un poco más sólidas.

Una respuesta más directa sería escribir archivos generales que "incluirían_once" en su archivo y que proporcionarían la usabilidad que necesitarían.Esto se dividiría en categorías y NO se proporcionaría en un archivo MASIVO "hooks.php".Eso sí, ojo, porque lo que acaba pasando es que los archivos que incluyen acaban teniendo cada vez más dependencias y la funcionalidad mejora.Intente mantener bajas las dependencias de API.Es decir, menos archivos para incluir.

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

Hace cumplir la interfaz de una clase de complemento, admite una interfaz de línea de comandos y no es demasiado difícil de poner en marcha, especialmente si lees la historia de portada al respecto en el Revista de arquitectos PHP.

Un buen consejo es mirar cómo lo han hecho otros proyectos.Muchos requieren tener complementos instalados y su "nombre" registrado para los servicios (como lo hace 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 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 verdaderamente orientado a objetos.

El Marco Zend hace uso de muchos métodos de enganche y está muy bien diseñado.Sería un buen sistema a considerar.

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

¿Qué pasaría si quisiera que los complementos se ejecutaran en un servidor remoto diferente?La mejor manera de hacerlo 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ía una llamada cURL a la URL proporcionada a su aplicación (por ejemplo, a través de https) donde los servidores remotos pueden realizar tareas basadas en la información enviada por su aplicación.

Esto proporciona dos beneficios:

  1. No es necesario 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)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top