Cel mai bun mod de a permite plugin-uri pentru o aplicație PHP

Incep o noua aplicatie web in PHP si de data asta vreau sa creez ceva ce oamenii pot extinde folosind o interfata plugin.

Cum se face scrierea de "cârlige" în codul lor, astfel încât pluginurile să se poată atașa la anumite evenimente?

0
adăugat editat
Vizualizări: 26

8 răspunsuri

Ai putea folosi un model de Observer. O modalitate funcțională simplă de a realiza acest lucru:

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

Ieșire:

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

Note:

Pentru acest exemplu de cod sursă, trebuie să declarați toate pluginurile înainte de codul sursă propriu-zis pe care doriți să îl puteți extinde. Am inclus un exemplu de modul în care să se ocupe de valori unice sau multiple care sunt transmise pluginului. Cea mai dificilă parte a acestui lucru este scrierea documentației actuale, care afișează ce argumente sunt transmise fiecărui cârlig.

Aceasta este doar o metodă de realizare a unui sistem de pluginuri în PHP. Există alternative mai bune, vă sugerăm să verificați documentația WordPress pentru mai multe informații.

Ne pare rău, se pare că caracterele de subliniere sunt înlocuite de entitățile HTML de Markdown? Pot să trimit din nou acest cod atunci când acest bug se rezolvă.

Editați: Nevermind, apare numai atunci când editați

0
adăugat
Notă pedantică: acest lucru nu este un exemplu al modelului de observator. Este un exemplu al Model de mediator . Adevăratul observator este o notificare pură, nu există nici un mesaj de transmitere sau notificare condiționată (și nici un manager central pentru controlul notificărilor). Nu face răspunsul greșit , dar trebuie remarcat faptul că opriți-i pe oameni să cheme lucrurile prin numele greșit ...
adăugat autor ircmaxell, sursa
Rețineți că pentru PHP> = 5.0 puteți implementa acest lucru folosind interfețele Observer / Subiect definite în SPL: php.net/manual/ro/class.splobserver.php
adăugat autor John Carter, sursa
Rețineți că atunci când utilizați mai multe cârlige / ascultători, trebuie să returnați numai șiruri de caractere sau matrice, nu ambele. Am implementat ceva similar pentru Hound CMS - getbutterfly.com/hound .
adăugat autor Ciprian, sursa

Metodele cârlig și ascultător sunt cele mai frecvent utilizate, dar există și alte lucruri pe care le puteți face. În funcție de dimensiunea aplicației și de cine vă permiteți să vedeți codul (acesta va fi un script FOSS sau ceva în casă), veți influența foarte mult modul în care doriți să permiteți pluginurile.

kdeloach are un exemplu frumos, dar implementarea lui și funcția de cârlig este puțin nesigur. V-aș cere să furnizați mai multe informații despre natura aplicației PHP și scrisorii dvs. Și cum vedeți pluginurile care se potrivesc.

+1 la kdeloach de la mine.

0
adăugat

Cred că cea mai ușoară cale ar fi să-l urmați pe sfatul lui Jeff și să aruncați o privire în jurul codului existent. Încercați să căutați Wordpress, Drupal, Joomla și alte cunoscute CMS-uri bazate pe PHP pentru a vedea cum arată și simt cârligele API. În acest fel puteți obține chiar și idei pe care probabil că nu le-ați gândit înainte de a face lucrurile un pic mai mult rușine.

Un răspuns mai direct ar fi să scrieți fișierele generale pe care le-ar "include_once" în fișierul lor, care ar oferi utilitatea de care ar avea nevoie. Aceasta ar fi împărțită în categorii și NU este furnizată într-un fișier MASSIVE "hooks.php". Fiți atent, totuși, pentru că ceea ce se termină până se întâmplă este că fișierele pe care le includ în cele din urmă având tot mai multe dependențe și funcționalitate îmbunătățește. Încercați să păstrați scăderea dependențelor API. I. Mai puține fișiere pentru a le include.

0
adăugat
Aș adăuga DokuWiki la lista sistemelor pe care ar putea să le aruncați o privire. Are un sistem de evenimente frumos care permite un ecosistem plugins bogat.
adăugat autor chiborg, sursa

Deci, să presupunem că nu doriți modelul Observer deoarece trebuie să vă schimbați metodele de clasă pentru a vă ocupa de sarcina de a asculta și doriți ceva generic. Și să presupunem că nu doriți să utilizați moștenire extinde , deoarece probabil ați moștenit deja în clasa dvs. din altă clasă. Nu ar fi grozav să existe o modalitate generică de a face orice clasă conectabilă fără prea mult efort ? Iată cum:

<?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
\n"; } public function sayName() { echo "
\nMy Name is: " . $this->Name . "
\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;

În Partea 1, asta ar trebui să includeți cu un apel require_once() în partea de sus a scriptului dvs. PHP. Încarcă clasele pentru a face ceva conectabil.

În Partea a 2-a, aici încărcăm o clasă. Rețineți că nu trebuie să fac nimic special pentru clasă, care este semnificativ diferit de modelul Observer.

În partea a 3-a, aceasta este locul în care ne schimbăm clasa în jurul valorii de a fi "pluggable" (adică, acceptă pluginuri care ne permit să suprascrieți metodele de clasă și proprietățile). De exemplu, dacă aveți o aplicație web, este posibil să aveți un registru de pluginuri și puteți să activați pluginurile aici. Observați și funcția Dog_bark_beforeEvent() . Dacă am setat $ mixed = 'BLOCK_EVENT' înainte de declarația de întoarcere, acesta va bloca câinele din latră și ar bloca, de asemenea, Dog_bark_afterEvent deoarece nu ar fi niciun eveniment.

În Partea 4, acesta este codul normal de funcționare, dar observați că ceea ce ați putea crede că ar funcționa nu se execută deloc. De exemplu, câinele nu anunță numele lui ca "Fido", ci "Coco". Câinele nu spune "miau", ci "Woof". Și când doriți să vă uitați la numele câinelui după aceea, veți găsi că este "Different" în loc de "Coco". Toate aceste înlocuiri au fost furnizate în partea 3.

Deci, cum funcționează acest lucru? Ei bine, să excludem eval() (pe care toată lumea spune că este "rău") și să excludă că nu este un model de Observer. Deci, modul în care funcționează este clasa goală mascată numită Pluggable, care nu conține metodele și proprietățile folosite de clasa Dog. Astfel, din moment ce aceasta se întâmplă, metodele magice se vor angaja pentru noi. De aceea, în părțile 3 și 4 ne confruntăm cu obiectul derivat din clasa Pluggable, și nu de clasa Dogului în sine. În schimb, lăsăm clasa Plugin să facă "atingerea" obiectului Dog pentru noi. (Dacă asta e un tip de model de design despre care nu știu - te rog să-mi spui.)

0
adăugat
Am citiți pe Wikipedia despre acest lucru și, whoa, ai dreptate! :)
adăugat autor Volomike, sursa
Nu este acesta un decorator?
adăugat autor MV., sursa

Iată o abordare pe care am folosit-o, este o încercare de a copia din mecanismul Qt semnale / sloturi, un fel de model de Observer. Obiectele pot emite semnale. Fiecare semnal are un ID în sistem - este compus din numele id-ului și al obiectului expeditorului Fiecare semnal poate fi legat la receptoare, care pur și simplu este un "apelabil" Folosiți o clasă de autobuz pentru a transmite semnalele oricărei persoane interesate să le primească Când se întâmplă ceva, trimiteți un semnal. Mai jos este și exemplu de implementare

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

?>
0
adăugat

Sunt surprins că majoritatea răspunsurilor de aici par a fi orientate spre pluginurile care sunt locale aplicației web, adică plugin-uri care rulează pe serverul web local.

Dar dacă doriți ca pluginurile să ruleze pe un server diferit - de la distanță? Cel mai bun mod de a face acest lucru ar fi să furnizați un formular care vă permite să definiți diferite adrese URL care ar fi numite atunci când apar anumite evenimente în aplicația dvs.

Diferitele evenimente ar trimite informații diferite bazate pe evenimentul care tocmai a avut loc.

În acest fel, veți efectua un apel cURL la adresa URL furnizată aplicației dvs. (de exemplu, peste https) în cazul în care serverele la distanță pot efectua activități pe baza informațiilor trimise de aplicația dvs.

Acest lucru oferă două avantaje:

  1. Nu trebuie să găzduiți niciun cod pe serverul dvs. local (securitate)
  2. Codul poate fi pe servere la distanță (extensibilitate) în alte limbi decât PHP (portabilitate)
0
adăugat
Acesta este mai mult un "push API" decât un sistem "plugin" - oferiți o modalitate ca alte servicii să primească notificări despre evenimentele selectate. Ceea ce se înțelege în general prin "pluginuri" este că puteți instala aplicația și apoi adăugați funcționalități pentru a personaliza comportamentul în scopurile dvs., ceea ce presupune ca pluginul să fie difuzat la nivel local - sau cel puțin să aveți o comunicație sigură și eficientă pe 2 căi pentru a oferi informația pentru aplicația nu doar să o ia de la . Cele două caracteristici sunt oarecum distincte, iar pentru mu
adăugat autor IMSoP, sursa

Există un proiect elegant denumit Stickleback de Matt Zandstra de la Yahoo care gestionează o mare parte din munca pentru manipularea plugin-urilor în PHP.

Îmbunătățește interfața unei clase de pluginuri, acceptă o interfață de linie de comandă și nu este prea greu pentru a intra în funcțiune - mai ales dacă citiți povestea despre el în Revista arhitect PHP .

0
adăugat

Sfat util este să te uiți cum au făcut alte proiecte. Mulți solicită ca pluginurile să fie instalate și numele lor să fie înregistrate pentru servicii (cum ar fi wordpress), astfel încât să aveți "puncte" în codul dvs. unde apelați o funcție care identifică ascultătorii înregistrați și le execută. Un model standard de design OO este Modelul de observator , care ar fi o opțiune bună de implementat în un sistem PHP cu adevărat orientat spre obiect.

Zend Framework folosește multe metode de conectare și este foarte frumos arhitect. Ar fi un sistem bun la care să te uiți.

0
adăugat