Лучший способ разрешить плагины для PHP-приложения


Я запускаю новое веб-приложение на PHP, и на этот раз я хочу создать что-то, что люди могут расширить с помощью интерфейса плагина.

Как можно написать "крючки" в свой код, чтобы Плагины могли прикрепляться к определенным событиям?

8 252

8 ответов:

можно использовать шаблон Observer. Простой функциональный способ сделать это:

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

выход:

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

Примечания:

для этого примера исходного кода необходимо объявить все плагины перед фактическим исходным кодом, который вы хотите расширить. Я включил пример того, как обрабатывать одно или несколько значений, передаваемых в плагин. Самая сложная часть этого-написание фактической документации, которая перечисляет, какие аргументы передаются каждому крючку.

это всего лишь один из способов выполнения системы плагинов в PHP. Есть лучшие альтернативы, я предлагаю вам проверить документацию WordPress для получения дополнительной информации.

извините, похоже, символы подчеркивания заменяются HTML-сущностями на Markdown? Я могу повторно опубликовать этот код, когда эта ошибка будет исправлена.

Edit: Nevermind, он появляется только тогда, когда вы находитесь редактирование

Итак, предположим, что вам не нужен шаблон наблюдателя, потому что он требует, чтобы вы изменили свои методы класса для обработки задачи прослушивания и хотите что-то общее. И допустим, вы не хотите использовать extends наследование, потому что вы уже можете наследовать в своем классе от какого-то другого класса. Не было бы здорово иметь общий способ сделать любой класс подключаемый без особых усилий? Вот как:

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

в части 1, это то, что вы могли бы включить с помощью require_once() звонок в верхней части вашего PHP-скрипта. Он загружает классы, чтобы сделать что-то вставные.

в части 2, где мы загружаем класс. Обратите внимание, что мне не нужно было делать ничего особенного для класса, который значительно отличается от шаблона наблюдателя.

в части 3, Вот где мы переключаем наш класс вокруг в бытие "pluggable" (то есть, поддерживает плагины, которые позволяют нам переопределить методы и свойства класса). Так, например, если у вас есть веб-приложение, вы можете есть плагин реестра, и вы можете активировать Плагины здесь. Обратите внимание также на . Если я установил $mixed = 'BLOCK_EVENT' перед оператором return он заблокирует собаку от лая, а также заблокирует Dog_bark_afterEvent, потому что не будет никакого события.

в части 4 это обычный код операции, но обратите внимание, что то, что вы могли бы подумать, будет работать совсем не так. Например, собака не объявляет свое имя как "Фидо", но "Коко". Собака не говорит "мяу", но "Гав". И когда вы хотите посмотреть на имя собаки после этого, вы обнаружите, что это "другое" вместо "Коко". Все эти переопределения были предусмотрены в части 3.

так как же это работает? Ну, давайте исключим eval() (что все говорят "зло") и исключают, что это не шаблон наблюдателя. Таким образом, способ его работы-это скрытый пустой класс под названием Pluggable, который не содержит методов и свойств, используемых классом Dog. Таким образом, поскольку это происходит, магические методы будем заниматься за нас. Вот почему в частях 3 и 4 мы имеем дело с объектом, производным от класса Pluggable, а не с самим классом Dog. Вместо этого мы позволяем классу плагина делать "прикосновение" к объекту Dog для нас. (Если это какой-то шаблон дизайна, о котором я не знаю, пожалуйста, дайте мне знать.)

The крючком и слушатель метод наиболее часто используется, но есть и другие вещи, которые вы можете сделать. В зависимости от размера вашего приложения и того, кому Вы разрешите увидеть код (это будет сценарий FOSS или что-то в доме), это сильно повлияет на то, как вы хотите разрешить Плагины.

У kdeloach есть хороший пример, но его реализация и функция крючка немного небезопасны. Я хотел бы попросить вас дать больше информации о характере php app ваше письмо, и как вы видите Плагины вписываются.

+1 к kdeloach от меня.

вот подход, который я использовал, это попытка скопировать из механизма сигналов/слотов Qt, своего рода шаблон наблюдателя. Объекты могут излучать сигналы. Каждый сигнал имеет идентификатор в системе - он состоит из идентификатора отправителя + имя объекта Каждый сигнал может быть привязан к приемникам, который просто является " вызываемым" Вы используете класс шины для передачи сигналов всем, кто заинтересован в их получении Когда что-то происходит, вы "посылаете" сигнал. Ниже приведен и пример реализации

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

?>

Я считаю, что самый простой способ-следовать собственным советам Джеффа и взглянуть на существующий код. Попробуйте посмотреть на Wordpress, Drupal, Joomla и другие известные PHP-CMS, чтобы увидеть, как выглядят и чувствуют их API-крючки. Таким образом, вы даже можете получить идеи, которые вы, возможно, не думали ранее, чтобы сделать вещи немного больше rubust.

более прямым ответом было бы написать общие файлы, которые они будут "include_once" в свой файл, который обеспечит удобство использования они потребоваться. Это было бы разбито на категории и не предусмотрено в одном массивном "крючке".PHP-файл. Будьте осторожны, потому что в конечном итоге происходит то, что файлы, которые они включают, в конечном итоге имеют все больше и больше зависимостей и улучшают функциональность. Старайтесь держать зависимости API на низком уровне. Т. е. меньше файлов для их включения.

есть аккуратный проект под названием Колюшка Мэтт Зандстра в Yahoo, который обрабатывает большую часть работы по обработке плагинов в PHP.

Он обеспечивает интерфейс класса плагинов, поддерживает интерфейс командной строки и не слишком сложно встать и запустить - особенно если Вы читаете историю обложки об этом в PHP architect magazine.

хороший совет, это посмотреть, как другие проекты сделали это. Многие требуют установки плагинов и их" имя", зарегистрированное для служб (например, wordpress), поэтому у вас есть" точки " в вашем коде, где вы вызываете функцию, которая идентифицирует зарегистрированных слушателей и выполняет их. Стандартный OO дизайн скороговоркой является Шаблон Observer, что было бы хорошим вариантом для реализации в действительно объектно-ориентированной системе PHP.

The Zend Framework использует многие методы подключения, и очень красиво спроектированы. Это была бы хорошая система, чтобы посмотреть.

Я удивлен, что большинство ответов здесь, похоже, ориентированы на плагины, которые являются локальными для веб-приложения, т. е. плагины, которые работают на локальном веб-сервере.

Что делать, если вы хотите, чтобы Плагины работали на другом удаленном сервере? Лучший способ сделать это-предоставить форму, которая позволяет определить различные URL-адреса, которые будут вызываться при возникновении определенных событий в вашем приложении.

разные события будут отправлять различную информацию основываясь на только что произошедшем событии.

таким образом, вы просто выполните вызов cURL на URL-адрес, который был предоставлен вашему приложению (например, через https), где удаленные серверы могут выполнять задачи на основе информации, отправленной вашим приложением.

Это дает два преимущества:

  1. вам не нужно размещать какой-либо код на локальном сервере (безопасность)
  2. код может быть на удаленных серверах (расширяемость) на разных языках кроме PHP (переносимость)