Nejlepší způsob, jak umožnit pluginy pro aplikace PHP

hlasů
245

Začínám novou webovou aplikaci v PHP a tentokrát chci vytvořit něco, co lidé mohou rozšířit pomocí plug-in rozhraní.

Jak se dá jít o psaní ‚háčky‘ do jejich kódu, aby se zásuvné moduly lze připojit k určité události?

Položena 01/08/2008 v 13:50
zdroj uživatelem
V jiných jazycích...                            


8 odpovědí

hlasů
148

Dalo by se použít Observer. Jednoduchý funkční způsob, jak toho dosáhnout:

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

Výstup:

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

Poznámky:

Pro tento příklad zdrojového kódu, musíte deklarovat všechny pluginy před samotným zdrojový kód, který chcete být opětovně. Já jsem byl přiložen příklad, jak zacházet s jednoduchými nebo vícenásobnými hodnoty jsou předávány do zásuvného modulu. Nejtěžší část je to psaní aktuální dokumentaci, která uvádí, jaké argumenty dostat předány každému háčku.

To je jen jeden způsob, jak toho dosáhnout plugin systém v PHP. Existují lepší alternativy, doporučuji vám vyzkoušet Dokumentace WordPress pro více informací.

Omlouváme se, ale zdá se znaky podtržení nahrazeny HTML entity by Markdown? Mohu znovu psát tento kód když se tato chyba opravena.

Edit: Nevadí, to jen zdá, že způsob, jakým při úpravách

Odpovězeno 01/08/2008 v 14:46
zdroj uživatelem

hlasů
51

Takže řekněme, že nechcete, aby se vzor Observer, protože to vyžaduje, abyste změnit své metody třídy zvládnout úkol poslouchat a chtějí něco druhový. A řekněme, že nechcete používat extendsdědičnost, protože může být již dědí ve své třídě z jiné třídy. Nebylo by skvělé mít obecný způsob, jak kterákoliv třída zásuvný bez větší námahy ? Zde je návod:

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

Části 1, to je to, co může zahrnovat s require_once()voláním v horní části vašeho PHP skriptu. Načte tříd, aby se něco zásuvné.

V části 2, které je místo, kde jsme načíst třídu. Všimněte si, že jsem nemusel dělat nic zvláštního do třídy, která je výrazně odlišná od Observer.

V části 3, to je místo, kde jsme se přepnete naši třídu kolem do bytí „zásuvné“ (to znamená, že podporuje pluginy, které umožňují nám přepsat metody třídy a vlastnosti). Tak například, pokud máte webovou aplikaci, můžete mít registru plug-in, a ty by mohly aktivovat pluginy zde. Všimněte si také Dog_bark_beforeEvent()funkci. Když nastavím $mixed = 'BLOCK_EVENT'před příkazem return, bude blokovat psa od štěkání a bude také blokovat Dog_bark_afterEvent, protože tam by neměly být žádné události.

V části 4, to je normální operační kód, ale všimněte si, že to, co si můžete myslet poběží neběží takhle vůbec. Například pes neoznamuje jeho jméno jako ‚Fido‘, ale ‚Coco‘. Pes neříká ‚mňau‘, ale ‚haf‘. A když budete chtít podívat na jméno psa poté, zjistíte, že je ‚Different‘ místo ‚Coco‘. Všechny tyto přepisy byly uvedeny v části 3.

Tak jak to funguje? Dobře, pojďme vyloučit eval()(které všichni říkají, že je „zlo“) a vyloučit, že to není Observer. Tak, jak to funguje, je záludná prázdná třída nazývá Výměnné, který neobsahuje metody a vlastnosti používaných třídou Dog. Jelikož k tomu dojde, metody magické zapojí pro nás. To je důvod, proč v částech 3 a 4, my si s objektem odvozené od Pluggable třídy, nikoliv třída psa sám. Místo toho, ať máme Plugin třída dělat „dojemné“ na objekt Dog pro nás. (V případě, že je nějaký druh návrhového vzoru Nevím o - dejte mi vědět.)

Odpovězeno 01/06/2009 v 06:59
zdroj uživatelem

hlasů
31

Hák a posluchač metoda je nejčastěji používán, ale existují i jiné věci, které můžete udělat. V závislosti na velikosti vaší aplikace, a kdo si bude umožní vidět kód (je to má být FOSS skript, nebo něco v domě) do značné míry ovlivní, jak chcete povolit pluginy.

kdeloach má pěkný příklad, ale jeho realizace a funkce hák je trochu nebezpečné. Chtěl bych se zeptat, jak poskytnout více informací o povaze aplikace php vašeho psaní, a jak vidíte plugins montáž dovnitř.

1 až kdeloach ode mne.

Odpovězeno 01/08/2008 v 18:23
zdroj uživatelem

hlasů
19

Zde je přístup, jsem použil, je to pokus o kopírování z Qt signály / sloty mechanismus, jakýsi Observer. Objekty mohou dávat signál. Každý signál má identifikátor v systému - to je složen ze jména id + objektu odesílatele každý signál může být vázané k přijímačům, které prostě je „splatné“ Můžete použít třídu sběrnice předávat signály komukoliv se zájmem o jejich obdržení Když se něco stane, „odeslat“ signál. Níže je uveden příklad a implementace

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

?>
Odpovězeno 25/09/2008 v 22:29
zdroj uživatelem

hlasů
14

Domnívám se, že nejjednodušší způsob, jak by bylo sledovat Jeffovu vlastní radu a podívat se kolem stávajícího kódu. Zkuste se podívat na Wordpress, Drupal, Joomla a další dobře známá PHP-based CMS je vidět, jak se jejich API háčky vypadat a cítit. Tímto způsobem se můžete dostat i nápady, které jste ani nenapadlo dříve, aby se věci trochu více hloubkových.

Více přímá odpověď bude psát obecné soubory, které by „include_once“ do svého souboru, který by poskytoval použitelnost by potřebují. To by být rozdělen do kategorií a NOT poskytovány v jednom MASSIVE souboru „hooks.php“. Buďte opatrní, protože to, co skončí děje, je, že soubory, které obsahují skončit s více a více závislostí a funkčnost zlepšuje. Snažte se udržet nízkou závislostí API. IE méně soubory pro ně zahrnout.

Odpovězeno 01/08/2008 v 14:44
zdroj uživatelem

hlasů
13

K dispozici je elegantní projekt s názvem stickleback Matt Zandstra na Yahoo, který zpracovává hodně práce pro zacházení s pluginy v PHP.

Prosazuje rozhraní třídy plugin podporuje rozhraní příkazové řádky a není příliš těžké se dostat nahoru a běh - zejména pokud si přečtete krycí příběh o tom v časopise PHP architekta .

Odpovězeno 17/09/2008 v 20:00
zdroj uživatelem

hlasů
10

Dobrá rada je podívat se, jak ostatní projekty to udělal. Mnoho volání za to, že zásuvné moduly nainstalovány a jejich „jména“, zapsaná pro služby (jako je WordPress dělá), takže máte „body“ v kódu, kam volají funkci, která identifikuje zaregistrovaných posluchačů a vykonává je. Standardní provedení OO patter je Observer , který by byl dobrou volbou realizovat ve skutečně objektově orientovaného PHP systému.

Zend Framework využívá mnoho způsobů závěsných a je velmi pěkně architected. To by bylo dobrý systém na pohled.

Odpovězeno 17/09/2008 v 20:38
zdroj uživatelem

hlasů
7

Jsem překvapen, že většina odpovědí zde se zdají být zaměřeny o pluginy, které jsou místní webové aplikace, tedy pluginy, které běží na lokálním serveru.

A co když jste chtěli pluginy běžet na jiném - vzdálený - server? Nejlepší způsob, jak to udělat, je poskytnout formu, která vám umožní definovat různé adresy URL, které by bylo nazýváno když dojde k určité události v aplikaci.

Různé akce pošle jiné informace založené na události, která právě nastala.

Tímto způsobem by se stačí provést cURL hovoru na adresu URL, která byla poskytnuta pro vaši aplikaci (například přes https), kde vzdálené servery mohou provádět úlohy na základě informací, která byla odeslána prostřednictvím aplikace.

To poskytuje dvě výhody:

  1. Nemusíte hostovat libovolný kód na lokálním serveru (security)
  2. Kód může být na vzdálených serverech (roztažnosti) v různých jazycích jiné než PHP (přenositelnost)
Odpovězeno 22/04/2013 v 08:41
zdroj uživatelem

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