Beste måten å tillate plugins for et PHP-programmet

stemmer
251

Jeg starter en ny web-applikasjon i PHP og denne gangen ønsker jeg å skape noe som folk kan utvide ved hjelp av en plugin-grensesnitt.

Hvordan gjør man om å skrive 'kroker' inn koden sin, slik at plugins kan feste til bestemte hendelser?

Publisert på 01/08/2008 klokken 12:50
kilden bruker
På andre språk...                            


8 svar

stemmer
151

Du kan bruke en Observer mønster. En enkel funksjonell måte å oppnå dette:

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

Produksjon:

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

Merknader:

For dette eksempelet kildekoden, må du deklarere alle plugins før selve kildekoden som du ønsker å være utvidbart. Jeg har tatt med et eksempel på hvordan man skal håndtere en eller flere verdier som sendes til plugin. Den vanskeligste delen av dette skriver den faktiske dokumentasjon som viser hva argumenter får gått til hver krok.

Dette er bare en fremgangsmåte for å oppnå en plugg system i PHP. Det er bedre alternativer, foreslår jeg at du sjekker ut WordPress Dokumentasjon for mer informasjon.

Beklager, det ser ut underst tegn er erstattet av HTML enheter med Markdown? Jeg kan re-poste denne koden når denne feilen blir fikset.

Edit: Glem det, bare ser det på den måten når du redigerer

Svarte 01/08/2008 kl. 13:46
kilden bruker

stemmer
52

Så la oss si at du ikke vil at Observer mønster fordi det krever at du endrer dine klassemetoder for å håndtere oppgaven med å lytte, og vil ha noe generisk. Og la oss si at du ikke ønsker å bruke extendsarv fordi du kan allerede være å arve i klassen fra en annen klasse. Ville det ikke vært flott å ha en generisk måte å gjøre noen klasse pluggbare uten store anstrengelser ? Dette er hvordan:

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

I del 1, det er hva du kan ta med en require_once()samtale på toppen av PHP script. Den laster klassene å gjøre noe pluggbare.

I del 2, det er der vi legger i en klasse. Merk jeg trengte ikke å gjøre noe spesielt for klassen, noe som er vesentlig annerledes enn Observer mønster.

I del 3, det er der vi slår vår klasse rundt til å bli "pluggbar" (det vil si, støtter plugins som lar oss overstyre klassen metoder og egenskaper). Så, for eksempel, hvis du har en web-app, du kan ha en plugin registret, og du kan aktivere plugins her. Legg også merke til den Dog_bark_beforeEvent()funksjonen. Hvis jeg satt $mixed = 'BLOCK_EVENT'før retur erklæringen, vil den blokkere hunden fra barking og vil også blokkere Dog_bark_afterEvent fordi det ikke ville være noen hendelse.

I del 4, som er den normale driften koden, men merker at det du kanskje tror ville kjøre kjører ikke sånn i det hele tatt. For eksempel, gjør at hunden ikke kunngjøre det navnet som 'Fido', men 'Coco'. Hunden sier ikke 'mjaue', men 'Woof'. Og når du ønsker å se på hundens navn etterpå, finner du det er 'annerledes' i stedet for 'Coco'. Alle de overstyrer ble gitt i del 3.

Så hvordan fungerer dette? Vel, la oss utelukke eval()(som alle sier er "onde") og utelukke at det ikke er en Observer mønster. Så, slik det fungerer er den sleipe tom klasse kalt Stiger, som ikke inneholder de metoder og egenskaper som brukes av Dog klasse. Dermed, siden det skjer, vil de magiske metoder engasjere for oss. Det er derfor i deler 3 og 4 vi rotet med gjenstanden stammer fra Stiger klassen, ikke hunden klassen selv. I stedet lar vi Plugin klassen gjør det "rørende" på hunden objekt for oss. (Hvis det er noen form for design mønster jeg vet ikke om - vennligst gi meg beskjed.)

Svarte 01/06/2009 kl. 05:59
kilden bruker

stemmer
33

Den krok og lytteren metoden er den mest brukte, men det er andre ting du kan gjøre. Avhengig av størrelsen på appen din, og som du kommer til å tillate se koden (dette kommer til å bli en FOSS script, eller noe i huset) vil påvirke sterkt hvordan du vil tillate plugins.

kdeloach har et fint eksempel, men gjennomføring og krok funksjonen er litt usikre. Jeg vil be for deg å gi mer informasjon om innholdet i php app å skrive, og hvordan du ser plugins montering i.

1 til kdeloach fra meg.

Svarte 01/08/2008 kl. 17:23
kilden bruker

stemmer
20

Her er en tilnærming jeg har brukt, er det et forsøk på å kopiere fra Qt signaler / spor mekanisme, en slags Observer mønster. Objekter kan avgi signaler. Hvert signal har en ID i systemet - det er komponert av avsenderens id + objektnavnet hver signal kan binded til mottakerne, som rett og slett er en "callable" Du bruker en buss klasse å passere signaler til alle som er interessert i å motta dem når noe skjer, du "sende" et signal. Nedenfor er og eksempel implementering

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

?>
Svarte 25/09/2008 kl. 21:29
kilden bruker

stemmer
15

Jeg tror den enkleste måten ville være å følge Jeff egen råd og ta en titt rundt eksisterende kode. Prøv å se på Wordpress, Drupal, Joomla og andre kjente PHP-basert CMS for å se hvordan deres API kroker ser og føler. På denne måten kan du selv få ideer du kanskje ikke har tenkt på tidligere for å gjøre ting litt mer rubust.

En mer direkte svar ville være å skrive generelle filer som de ville "include_once" i sin fil som ville gi brukervennligheten de ville trenge. Dette vil bli delt opp i kategorier, og IKKE gitt i en MASSIVE "hooks.php" fil. Vær forsiktig, fordi det ender opp som skjer er at filer som de inneholder ende opp med å ha flere og flere avhengigheter og funksjonalitet blir bedre. Prøv å holde API avhengig lav. IE færre filer for dem å inkludere.

Svarte 01/08/2008 kl. 13:44
kilden bruker

stemmer
13

Det er en ryddig prosjekt kalt Stickleback av Matt Zandstra på Yahoo som håndterer mye av arbeidet for håndtering av plugins i PHP.

Det håndhever grensesnittet til en plugin klasse, støtter en kommandolinje-grensesnitt, og er ikke så vanskelig å komme i gang - spesielt hvis du leser dekselet historien om det i PHP arkitekt magasinet .

Svarte 17/09/2008 kl. 19:00
kilden bruker

stemmer
10

Gode råd er å se hvordan andre prosjekter har gjort det. Mange samtale for å ha plugins installert, og deres "navn" registrert for tjenester (som wordpress gjør), så du må "poeng" i koden din, der du kaller en funksjon som identifiserer registrerte lyttere og utfører dem. En standard OO utformingen patter er Observer mønster , noe som ville være et godt alternativ å gjennomføre i en virkelig objektorientert PHP system.

Den Zend Framework gjør bruk av mange krok metoder, og er meget pent bygd. Det ville være et godt system for å se på.

Svarte 17/09/2008 kl. 19:38
kilden bruker

stemmer
7

Jeg er overrasket over at de fleste av svarene her synes å være rettet om plugins som er lokale for webapplikasjonen, dvs. plugins som kjører på den lokale webserveren.

Hva om du ønsket plugins for å kjøre på en annen - ekstern - server? Den beste måten å gjøre dette ville være å gi en form som gjør det mulig å definere ulike nettadresser som ville bli kalt når bestemte hendelser oppstår i programmet.

Ulike hendelser ville sende forskjellig informasjon basert på hendelsen som nettopp skjedde.

På denne måten vil du bare utføre en cURL kall til URL som er gitt til søknaden din (f.eks over HTTPS) hvor eksterne servere kan utføre oppgaver basert på informasjon som er blitt sendt av søknaden din.

Dette gir to fordeler:

  1. Du trenger ikke å være vert for noen kode på din lokale server (sikkerhet)
  2. Koden kan være på eksterne servere (utvidelses) på forskjellige språk andre da PHP (portabilitet)
Svarte 22/04/2013 kl. 07:41
kilden bruker

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