Beste manier om plugins voor een PHP applicatie toestaan

stemmen
245

Ik ben het starten van een nieuwe webapplicatie in PHP en deze keer wil ik iets dat mensen kunnen uitbreiden met behulp van een plugin-interface te creëren.

Hoe gaat een over het schrijven van 'hooks' in hun code, zodat plugins kan hechten aan bepaalde gebeurtenissen?

De vraag is gesteld op 01/08/2008 om 13:50
bron van user
In andere talen...                            


8 antwoorden

stemmen
14

Ik denk dat de makkelijkste manier zou zijn om Jeff's eigen advies te volgen en kijk eens rond bestaande code. Probeer te kijken naar Wordpress, Drupal, Joomla en andere bekende PHP-gebaseerde CMS om te zien hoe hun API haken look en feel. Op deze manier kunt u zelfs ideeën die je misschien niet aan gedacht hebben eerder om dingen te maken een beetje meer rubust krijgen.

Een meer direct antwoord zou zijn om algemene bestanden die ze zouden "include_once" in hun bestand dat zou de bruikbaarheid zouden ze nodig hebben te schrijven. Dit zou worden opgedeeld in categorieën en niet voorzien in een MASSIVE "hooks.php" bestand. Wees wel voorzichtig, want wat uiteindelijk gebeurt, is dat bestanden die ze bevatten eindigen met meer en meer afhankelijkheden en functionaliteit verbetert. Probeer API afhankelijkheden laag te houden. IE minder bestanden voor hen op te nemen.

antwoordde op 01/08/2008 om 14:44
bron van user

stemmen
148

Je zou een Observer patroon te gebruiken. Een eenvoudige functionele manier om dit te bereiken:

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

Output:

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

Opmerkingen:

Voor dit voorbeeld broncode, moet je al je plugins voor de eigenlijke broncode die u wilt uitbreidbaar tot verklaard. Ik heb een voorbeeld van hoe om te gaan enkele of meerdere waarden worden doorgegeven aan de plugin inbegrepen. Het moeilijkste onderdeel hiervan is het schrijven van de feitelijke documentatie die laat zien wat argumenten krijgen doorgegeven aan elke haak.

Dit is slechts één werkwijze voor het bereiken van een plugin-systeem in PHP. Er zijn betere alternatieven, ik stel voor dat u kijken op de WordPress Documentatie voor meer informatie.

Probeer het later underscore karakters worden vervangen door HTML-entiteiten die door Markdown? Ik kan re-post deze code wanneer deze bug gerepareerd.

Edit: Nevermind, het lijkt alleen maar zo als u bewerkt

antwoordde op 01/08/2008 om 14:46
bron van user

stemmen
31

De haak en luisteraar methode is de meest gebruikte, maar er zijn andere dingen die je kunt doen. Afhankelijk van de grootte van uw app, en wie je gaat om te zien de code (dit is van plan om een FOSS script, of iets in huis zijn) zal een grote invloed op de manier waarop u wilt plugins mogelijk te maken.

kdeloach heeft een mooi voorbeeld, maar zijn de uitvoering en de haak functie is een beetje onveilig. Ik zou vragen voor u om meer informatie over de aard van php app uw schrijven te geven, En hoe zie je plugins fitting in.

+1 tot kdeloach van mij.

antwoordde op 01/08/2008 om 18:23
bron van user

stemmen
13

Er is een nette project genaamd Stickleback door Matt Zandstra bij Yahoo, dat een groot deel van het werk voor de behandeling van plugins in PHP afhandelt.

Het dwingt de interface van een plug-klasse, ondersteunt een command line interface en is niet al te moeilijk te krijgen up and running - vooral als je de cover story over lezen in de PHP architect tijdschrift .

antwoordde op 17/09/2008 om 20:00
bron van user

stemmen
10

Een goed advies is om te kijken hoe andere projecten hebben het gedaan. Veel oproep voor het feit dat plugins geïnstalleerd en hun "naam" ingeschreven voor diensten (zoals WordPress doet), dus je moet "punten" in de code waar je een functie die geregistreerde listeners identificeert en voert hen te bellen. Een standaard OO ontwerp babbel is de Observer Pattern , die een goede optie om te implementeren in een echt object georiënteerd PHP-systeem zou zijn.

Het Zend Framework maakt gebruik van veel haken methoden, en is zeer mooi architected. Dat zou een goed systeem om naar te kijken.

antwoordde op 17/09/2008 om 20:38
bron van user

stemmen
19

Hier is een benadering die ik heb gebruikt, het is een poging om te kopiëren van Qt signalen / sleuven mechanisme, een soort Observer patroon. Objecten kunnen signalen uitzenden. Elk signaal heeft een ID in het systeem - het is samengesteld door id + object naam van de afzender van elk signaal kan worden binded naar de ontvangers, die eenvoudig een "callable" U gebruikt een bus klasse aan de signalen naar iedereen die geïnteresseerd is in passeren ze te ontvangen Als er iets gebeurt, "send" een signaal. Hieronder is en voorbeeld implementatie

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

?>
antwoordde op 25/09/2008 om 22:29
bron van user

stemmen
51

Dus laten we zeggen dat je niet wilt dat de Observer patroon, omdat het vereist dat u uw methoden van de klasse veranderen om de taak van het luisteren te behandelen, en iets algemeens wilt. En laten we zeggen dat je niet wilt gebruiken extendserfenis, omdat je al kunnen erven in je klas tegen een andere klasse. Zou het niet geweldig zijn om een generieke manier om te maken een klasse pluggable zonder veel inspanning ? Hier is hoe:

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

In deel 1, dat is wat je zou kunnen omvatten met een require_once()oproep aan de bovenkant van uw PHP-script. Het laadt de klassen om iets pluggable te maken.

In deel 2, dat is waar we een klasse laden. NB Ik heb niet om het even wat speciaal voor de klasse, die sterk afwijken van de Observer patroon te doen.

In deel 3, dat is waar we onze klas rond te schakelen naar zijn "pluggable" (dat wil zeggen, ondersteunt plug-ins die laten overschrijven klasse methoden en eigenschappen). Dus, bijvoorbeeld, als je een web-app, kunt u een plug-register, en je kon plugins hier te activeren. Let ook op de Dog_bark_beforeEvent()functie. Als ik te stellen $mixed = 'BLOCK_EVENT'voor de return statement, zal het de hond blaffen te blokkeren en zou ook de Dog_bark_afterEvent blokkeren omdat er geen geval zou zijn.

In deel 4, dat is de normale werking code, maar merken dat wat je zou denken zou lopen niet lopen als dat helemaal. Zo hoeft de hond niet zijn naam aan te kondigen als 'Fido', maar 'Coco'. De hond zegt niet 'miauw', maar 'Woof'. En als je wilt daarna kijken naar de naam van de hond, vind je het is 'Different' in plaats van 'Coco'. Al die overschrijvingen werden voorzien in deel 3.

Dus hoe werkt dit? Nou, laten we uit te sluiten eval()(wat iedereen zegt is "het kwaad") en uit te sluiten dat het niet een Observer patroon. Dus, de manier waarop het werkt is de stiekeme lege klasse met de naam Pluggable, waarin de methoden en eigenschappen die door de klasse Dog bevat. Aangezien dus dat gebeurt, zal de magie methoden nemen voor ons. Dat is de reden waarom in deel 3 en 4 hebben we knoeien met het object afgeleid van de Pluggable klasse, niet de Hond klasse zelf. In plaats daarvan, we laten de Plugin klasse doen de "aanraking" op het object Hond voor ons. (Als dat is een soort van het ontwerp patroon dat ik weet niet hoe het - laat het me weten.)

antwoordde op 01/06/2009 om 06:59
bron van user

stemmen
7

Ik ben verbaasd dat de meeste antwoorden hier lijken te zijn gericht over plugins die lokaal zijn voor de webapplicatie zijn, dat wil zeggen, plugins die worden uitgevoerd op de lokale webserver.

Hoe zit het als je wilde dat de plug-ins te draaien op een ander - op afstand - server? De beste manier om dit te doen zou zijn om een ​​vorm die u toelaat om verschillende URL's die genoemd zou worden wanneer bepaalde gebeurtenissen zich voordoen in uw aanvraag te omschrijven.

Verschillende gebeurtenissen zouden andere informatie op basis van de gebeurtenis die alleen plaatsvond sturen.

Op deze manier zou je gewoon een cURL oproep uit te voeren om de URL die naar uw aanvraag is ontvangen (bijvoorbeeld over https), waar servers op afstand taken kunnen uitvoeren op basis van informatie die door uw aanvraag is verzonden.

Dit levert twee voordelen:

  1. Je hoeft niet aan een code te hosten op uw lokale server (security)
  2. De code kan worden op externe servers (rekbaarheid) in verschillende talen anders dan PHP (draagbaarheid)
antwoordde op 22/04/2013 om 08:41
bron van user

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