Сведения о вопросе

Ayrat

16:03, 1st July, 2020

Теги

php   plugins   architecture   hook    

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

Просмотров: 546   Ответов: 8

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

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



  Сведения об ответе

crush

18:03, 1st July, 2020

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

<?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: неважно, он появляется только тогда, когда вы редактируете


  Сведения об ответе

DINO

18:03, 1st July, 2020

Итак, предположим, что вам не нужен шаблон наблюдателя, потому что он требует, чтобы вы изменили методы своего класса для обработки задачи прослушивания, и хотите что-то общее. И предположим, что вы не хотите использовать 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 именно там мы переключаем наш класс на being "pluggable" (то есть, поддерживает плагины, которые позволяют нам переопределять методы и свойства класса). Так, например, если у вас есть веб-приложение, у вас может быть реестр плагинов, и вы можете активировать Плагины здесь. Обратите внимание также на функцию Dog_bark_beforeEvent() . Если я установлю $mixed = 'BLOCK_EVENT' перед оператором return, он заблокирует собаку от лая и также заблокирует Dog_bark_afterEvent, потому что не будет никакого события.

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

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


  Сведения об ответе

PAGE

18:03, 1st July, 2020

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

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

+1 к kdeloach от меня.


  Сведения об ответе

ITSME

18:03, 1st July, 2020

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

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

?>


  Сведения об ответе

Chhiki

18:03, 1st July, 2020

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

Более прямым ответом было бы написать общие файлы, которые они будут "include_once" в свой файл, который обеспечит удобство использования, в котором они будут нуждаться. Это будет разбито на категории и NOT представлено в одном файле MASSIVE "hooks.php". Однако будьте осторожны, потому что в конечном итоге происходит то, что файлы, которые они включают, имеют все больше и больше зависимостей и улучшается функциональность. Старайтесь держать API зависимостей на низком уровне. I.E меньше файлов для их включения.


  Сведения об ответе

+-*/

18:03, 1st July, 2020

Есть аккуратный проект под названием Stickleback от Matt Zandstra в Yahoo, который обрабатывает большую часть работы по обработке плагинов в PHP.

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


  Сведения об ответе

fo_I_K

18:03, 1st July, 2020

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

Фреймворк Zend использует множество методов закрепления и очень хорошо спроектирован. Это была бы хорошая система, чтобы посмотреть на нее.


  Сведения об ответе

PHPH

18:03, 1st July, 2020

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

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

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

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

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться