Melhor maneira de permitir que plugins para uma aplicação PHP

votos
245

Estou começando uma nova aplicação web em PHP e desta vez eu quero criar algo que as pessoas podem estender usando uma interface plug-in.

Como se faz para escrever 'ganchos' em seu código para que plugins pode anexar a eventos específicos?

Publicado 01/08/2008 em 13:50
fonte usuário
Em outras línguas...                            


8 respostas

votos
148

Você pode usar um padrão Observer. Uma maneira simples e funcional para fazer isso:

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

Saída:

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

Notas:

Para este código fonte de exemplo, você deve declarar todos os seus plugins antes de o código fonte real que você quer ser extensível. Eu incluí um exemplo de como lidar com valores únicos ou múltiplos sendo passado para o plugin. A parte mais difícil deste está escrevendo a documentação real que lista quais argumentos são passados ​​para cada gancho.

Este é apenas um método de realizar um sistema de plugins em PHP. Existem alternativas melhores, eu sugiro que você verifique a documentação do WordPress para mais informações.

Desculpe, parece caracteres sublinhados são substituídos por entidades HTML por Markdown? I pode voltar a publicar este código quando este bug fica fixo.

Edit: Não importa, ele só aparece assim quando você está editando

Respondeu 01/08/2008 em 14:46
fonte usuário

votos
51

Então, digamos que você não quer que o padrão Observer porque requer que você mudar seus métodos de classe para lidar com a tarefa de ouvir, e quer algo genérico. E vamos dizer que você não quer usar extendsherança, porque você pode já estar herdando em sua classe de alguma outra classe. Não seria ótimo ter uma forma genérica para fazer qualquer classe pluggable sem muito esforço ? Veja 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;

Na Parte 1, que é o que você pode incluir com uma require_once()chamada no topo do seu script PHP. Ele carrega as classes para fazer algo pluggable.

Na Parte 2, que é onde nós carregar uma classe. Nota eu não tenho que fazer nada especial para a classe, que é significativamente diferente do que o padrão Observer.

Na Parte 3, que é onde nós mudar a nossa classe em torno à existência "pluggable" (isto é, suporta plugins que nos permitem substituir métodos de classe e propriedades). Assim, por exemplo, se você tiver um aplicativo web, você pode ter um registro plugin, e você poderia ativar plugins aqui. Observe também a Dog_bark_beforeEvent()função. Se eu definir $mixed = 'BLOCK_EVENT'antes a instrução de retorno, ele irá bloquear o cão de latir e também bloquear a Dog_bark_afterEvent porque não haveria qualquer evento.

Na Parte 4, que é o código de operação normal, mas perceber que o que você poderia pensar que seria executado não funciona assim em tudo. Por exemplo, o cão não anunciá-lo de nome como 'Fido', mas 'Coco'. O cão não dizer 'miau', mas 'Woof'. E quando você quer olhar para o nome do cão depois, você achar que é 'diferente' em vez de 'Coco'. Todas essas substituições foram fornecidos na Parte 3.

Então, como isso funciona? Bem, vamos descartar eval()(que todo mundo diz que é "mal") e descarta que não é um padrão Observer. Assim, a forma como ele funciona é a classe vazia sorrateira chamado conectável, que não contém os métodos e propriedades usados pela classe Dog. Assim, uma vez que ocorre, os métodos mágicos irá envolver para nós. É por isso que nas partes 3 e 4 que mexer com o objeto derivado da classe Pluggable, não a classe Dog si. Em vez disso, nós deixamos a classe Plugin fazer o "tocar" no objeto Dog para nós. (Se isso é algum tipo de padrão de design Eu não sei sobre - por favor me avise.)

Respondeu 01/06/2009 em 06:59
fonte usuário

votos
31

O gancho e ouvinte método é o mais comumente usado, mas há outras coisas que você pode fazer. Dependendo do tamanho do seu aplicativo, e que o seu vai permitir ver o código (é que isto vai ser um script FOSS, ou algo em casa) irá influenciar muito como você deseja permitir que plugins.

kdeloach tem um bom exemplo, mas a sua implementação e função de gancho é um pouco inseguro. Gostaria de pedir para você dar mais informações sobre a natureza da aplicação php sua escrita, e como você vê plugins montagem no.

+1 para kdeloach de mim.

Respondeu 01/08/2008 em 18:23
fonte usuário

votos
19

Aqui está uma abordagem que eu usei, é uma tentativa de copiar de Qt mecanismo de sinais / slots de, uma espécie de padrão Observer. Os objectos podem emitir sinais. Cada sinal tem um ID no sistema - é composto pelo nome do ID + objeto do remetente Cada sinal pode ser binded para os receptores, que simplesmente é um "exigível" Você usa uma classe ônibus para passar os sinais para qualquer pessoa interessada em recebê-los Quando algo acontece, você "enviar" um sinal. Abaixo está e exemplo de implementação

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

?>
Respondeu 25/09/2008 em 22:29
fonte usuário

votos
14

Eu acredito que a maneira mais fácil seria seguir próprio conselho de Jeff e ter um olhar em torno do código existente. Tente olhar para Wordpress, Drupal, Joomla e do outro CMS baseado em PHP bem conhecido para ver como os seus ganchos de API olhar e sentir. Desta forma, você pode até ter idéias que você pode não ter pensado anteriormente para tornar as coisas um pouco mais rubust.

A resposta mais direta seria escrever arquivos gerais que eles iriam "include_once" em seu arquivo que iria proporcionar a usabilidade que seria necessário. Isso seria dividido em categorias e não previstos em um MASSIVE arquivo "hooks.php". Tenha cuidado, porém, porque o que acaba acontecendo é que os arquivos que incluem acabar tendo mais e mais dependências e funcionalidade melhora. Tente manter dependências API baixa. IE menos arquivos para que eles incluem.

Respondeu 01/08/2008 em 14:44
fonte usuário

votos
13

Há um projeto puro chamado Stickleback por Matt Zandstra no Yahoo que controla grande parte do trabalho para lidar com plugins em PHP.

Ele reforça a interface de uma classe plugin, suporta uma interface de linha de comando e não é muito difícil de se levantar e correr - especialmente se você ler a reportagem de capa sobre ele na revista PHP arquiteto .

Respondeu 17/09/2008 em 20:00
fonte usuário

votos
10

Um bom conselho é olhar como outros projetos de tê-lo feito. Muitos chamam por ter plugins instalados e seu "nome" registada para serviços (como o WordPress faz), então você tem "pontos" em seu código onde você chamar uma função que identifica ouvintes registrados e os executa. A patter projeto OO padrão é o padrão Observer , o que seria uma boa opção para implementar em um sistema PHP verdadeiramente orientada a objeto.

O Zend Framework faz uso de vários métodos de engate, e é muito bem arquitetado. Isso seria um bom sistema para olhar.

Respondeu 17/09/2008 em 20:38
fonte usuário

votos
7

Surpreende-me que a maioria das respostas aqui parecem ser orientados sobre plugins que são locais para a aplicação web, ou seja, plugins que rodam no servidor web local.

Que tal se você queria que os plugins para executar em um diferente - remoto - servidor? A melhor maneira de fazer isso seria para fornecer uma forma que permite que você defina URLs diferentes que seriam chamados quando determinados eventos ocorrem em sua aplicação.

Diferentes eventos iria enviar informações diferentes com base no evento que acabou de ocorrer.

Dessa forma, você só iria realizar uma chamada cURL para a URL que foi fornecida para a sua aplicação (por exemplo, sobre https), onde servidores remotos podem executar tarefas com base na informação que foi enviada pelo seu aplicativo.

Isto fornece dois benefícios:

  1. Você não tem para sediar qualquer código em seu servidor local (segurança)
  2. O código pode ser em servidores remotos (extensibilidade) em diferentes idiomas outro então PHP (portabilidade)
Respondeu 22/04/2013 em 08:41
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more