Najlepszy sposób zezwalania na wtyczki do aplikacji PHP

Uruchamiam nową aplikację internetową w PHP i tym razem chcę stworzyć coś, co ludzie mogą rozszerzyć za pomocą interfejsu wtyczki.

Jak można pisać "Hooki" w kodzie, aby wtyczki mogły się dołączać do konkretnych zdarzeń?

Author: Abdulla Nilam, 2008-08-01

8 answers

Przydałby się wzór obserwatora. Prosty funkcjonalny sposób na osiągnięcie tego:

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

Wyjście:

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

Uwagi:

Dla tego przykładowego kodu źródłowego, musisz zadeklarować wszystkie wtyczki przed rzeczywistym kodem źródłowym, które chcesz rozszerzyć. Załączyłem przykład, jak obsługiwać jedną lub wiele wartości przekazywanych do wtyczki. Najtrudniejszą częścią tego jest napisanie rzeczywistej dokumentacji, która wymienia, jakie argumenty zostaną przekazane do każdy hak.

Jest to tylko jedna z metod tworzenia systemu wtyczek w PHP. Istnieją lepsze alternatywy, proponuję sprawdzić dokumentację WordPress, aby uzyskać więcej informacji.

Przepraszam, wygląda na to, że znaki podkreślenia są zastępowane przez encje HTML przez Markdown? Mogę ponownie wysłać ten kod, gdy ten błąd zostanie naprawiony.

Edit: Nevermind, pojawia się tylko wtedy, gdy edytujesz

 151
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-20 11:18:33

Więc powiedzmy, że nie chcesz wzorca obserwatora, ponieważ wymaga to zmiany metod klasowych, aby poradzić sobie z zadaniem słuchania i chcesz czegoś ogólnego. Powiedzmy, że nie chcesz używać dziedziczenia extends, ponieważ możesz już dziedziczyć w swojej klasie od innej klasy. Czy nie byłoby wspaniale mieć ogólny sposób, aby każda klasa była pluggable bez większego wysiłku ? Oto jak:

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

W części 1, to możesz dołączyć do require_once() rozmowy w najlepszy skrypt PHP. Ładuje klasy, aby coś podłączyć.

W CZĘŚCI 2, tam ładujemy klasę. Uwaga nie musiałem robić nic specjalnego dla klasy, co znacznie różni się od wzorca obserwatora.

W części 3, w tym miejscu zamieniamy naszą klasę na "pluggable" (czyli wspiera wtyczki, które pozwalają nam nadpisać metody i właściwości klasy). Na przykład, jeśli masz aplikację internetową, możesz mieć rejestr wtyczek i możesz Aktywuj wtyczki tutaj. Zwróć również uwagę na funkcję Dog_bark_beforeEvent(). Jeśli ustawiłem $mixed = 'BLOCK_EVENT' przed instrukcją return, zablokuje ona psa przed szczekaniem i również zablokuje Dog_bark_afterEvent, ponieważ nie byłoby żadnego zdarzenia.

W części 4, to jest normalny kod działania, ale zauważ, że to, co myślisz, że będzie działać, nie działa tak w ogóle. Na przykład pies nie ogłasza swojego imienia jako "Fido", ale "Coco". Pies nie mówi "miau", ale "Woof". A kiedy chcesz spojrzeć na imię psa później okazuje się "inne" zamiast "Coco". Wszystkie te przesłanki zostały przedstawione w części 3.

Jak to działa? Cóż, wykluczmy eval() (co wszyscy mówią, że jest "złe") i wykluczmy, że nie jest to wzór obserwatora. Tak więc, sposób, w jaki to działa, to podstępna pusta Klasa o nazwie Pluggable, która nie zawiera metod i właściwości używanych przez klasę psa. Tak więc, ponieważ to nastąpi, magiczne metody zaangażują się dla nas. Dlatego w częściach 3 i 4 zadzieramy z obiekt wywodzący się z klasy Pluggable, a nie samej klasy psa. Zamiast tego pozwalamy klasie Plugin "dotykać" obiektu psa za nas. (Jeśli to jakiś wzór, o którym Nie wiem ... proszę dać mi znać . )
 52
Author: Volomike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-15 16:03:48

Metoda hook i listener jest najczęściej używana, ale są inne rzeczy, które możesz zrobić. W zależności od wielkości aplikacji, a kto będzie pozwolić zobaczyć kod (czy to będzie skrypt FOSS, lub coś w domu) będzie miał duży wpływ na to, jak chcesz zezwolić wtyczki.

Kdeloach ma ładny przykład, ale jego implementacja i funkcja Hooka są trochę niebezpieczne. Prosiłbym o podanie więcej informacji na temat charakteru aplikacji php, którą piszesz, I jak widzisz wtyczki pasujące.

+1 do kdeloach ode mnie.

 33
Author: w-ll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-01 17:23:43

Oto podejście, którego użyłem, próba skopiowania z mechanizmu sygnałów/slotów Qt, rodzaj wzorca obserwatora. Obiekty mogą emitować sygnały. Każdy sygnał ma ID w systemie-składa się z id nadawcy + nazwa obiektu Każdy sygnał może być wiązany do odbiorników, co po prostu jest " wywoływalne" Używasz klasy autobusu, aby przekazać sygnały każdemu, kto jest zainteresowany ich odbiorem Kiedy coś się dzieje, "wysyłasz" sygnał. Poniżej i przykład implementacji

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

?>
 20
Author: andy.gurin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-03 13:57:14

Uważam, że najłatwiej byłoby zastosować się do własnych rad Jeffa i rozejrzeć się po istniejącym kodzie. Spróbuj spojrzeć na Wordpress, Drupal, Joomla i inne znane CMS oparte na PHP, aby zobaczyć, jak wyglądają ich haki API. W ten sposób możesz nawet uzyskać pomysły, o których wcześniej nie myślałeś, aby uczynić rzeczy nieco bardziej rubust.

Bardziej bezpośrednią odpowiedzią byłoby zapisanie ogólnych plików, które "include_once" do ich pliku, który zapewniałby potrzebną im użyteczność. Byłoby to podzielone na kategorie, a nie w jednym masywnym " haku.php" plik. Bądź jednak ostrożny, ponieważ kończy się to tym, że pliki, które zawierają, mają coraz więcej zależności i poprawiają funkcjonalność. Staraj się utrzymywać niskie zależności API. Czyli mniej plików do ich włączenia.

 15
Author: helloandre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-01 13:44:35

Istnieje schludny projekt o nazwie Stickleback autorstwa Matta Zandstry z Yahoo, który zajmuje się wieloma pracami związanymi z obsługą wtyczek w PHP.

Wymusza interfejs klasy plugin, obsługuje interfejs wiersza poleceń i nie jest zbyt trudne do uruchomienia - zwłaszcza jeśli przeczytasz o tym okładkę w PHP architect magazine.

 13
Author: julz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-25 21:01:19

Dobrą radą jest sprawdzenie, jak zrobiły to inne projekty. Wiele z nich wymaga zainstalowania wtyczek i zarejestrowania ich " nazwy "dla usług (tak jak wordpress), więc masz" punkty " w kodzie, w którym wywołujesz funkcję, która identyfikuje zarejestrowanych słuchaczy i wykonuje je. Standardowym wzorcem projektu oo jest Observer Pattern, który byłby dobrym rozwiązaniem do zaimplementowania w prawdziwie obiektowym systemie PHP.

Zend Framework wykorzystuje wiele metody, i jest bardzo ładnie zaprojektowany. To byłby dobry system do obejrzenia.

 10
Author: THEMike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-17 19:38:52

Jestem zaskoczony, że większość odpowiedzi tutaj wydają się być nastawione na wtyczki, które są lokalne do aplikacji internetowej, czyli wtyczki, które działają na lokalnym serwerze WWW.

A jeśli chcesz, aby wtyczki działały na innym-zdalnym-serwerze? Najlepszym sposobem na to byłoby dostarczenie formularza, który pozwala zdefiniować różne adresy URL, które będą wywoływane, gdy określone zdarzenia wystąpią w aplikacji.

Różne zdarzenia będą wysyłać różne informacje na podstawie zdarzenie, które właśnie miało miejsce.

W ten sposób można po prostu wykonać wywołanie cURL do adresu URL, który został dostarczony do aplikacji (np przez https), gdzie zdalne serwery mogą wykonywać zadania na podstawie informacji, które zostały wysłane przez aplikację.

Daje to dwie korzyści:

  1. nie musisz hostować żadnego kodu na lokalnym serwerze (bezpieczeństwo)
  2. Kod może znajdować się na serwerach zdalnych (rozszerzalność) w różnych językach innych niż PHP (przenośność)
 7
Author: Tim Groeneveld,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-22 07:41:24