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

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

Как можно написать «хуки» в свой код, чтобы плагины могли прикрепляться к определенным событиям?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
285
0
38 196
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

Я бы добавил «ДокуВики» в список систем, на которые вы можете взглянуть. У него хорошая система событий, которая позволяет создать обширную экосистему плагинов.

chiborg 03.08.2010 17:53
Ответ принят как подходящий

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

<?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 для получения дополнительной информации.

Обратите внимание, что для PHP> = 5.0 вы можете реализовать это, используя интерфейсы Observer / Subject, определенные в SPL: php.net/manual/en/class.splobserver.php

John Carter 25.02.2010 17:47

Педантичное замечание: это не пример паттерна Наблюдатель. Это пример Mediator Pattern. Истинные наблюдатели - это чисто уведомления, нет передачи сообщений или условных уведомлений (также нет центрального менеджера для управления уведомлениями). Это не дает ответа неправильный, но следует отметить, чтобы люди не называли вещи неправильными именами ...

ircmaxell 10.02.2011 02:35

Обратите внимание, что при использовании нескольких обработчиков / прослушивателей вы должны возвращать только строки или массивы, а не то и другое одновременно. Я реализовал нечто подобное для Hound CMS - getbutterfly.com/hound.

Ciprian 19.12.2017 14:21

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

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

+1 кделоач от меня.

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

Он обеспечивает интерфейс класса подключаемого модуля, поддерживает интерфейс командной строки, и его не так сложно запустить и запустить, особенно если вы прочитали его обложку в Журнал архитектора PHP.

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

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

Вот подход, который я использовал, это попытка скопировать из механизма сигналов / слотов 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();

?>

Итак, допустим, вам не нужен шаблон Observer, потому что он требует, чтобы вы изменили методы вашего класса для обработки задачи прослушивания и хотите чего-то общего. Допустим, вы не хотите использовать наследование 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-скрипта. Он загружает классы, чтобы сделать что-нибудь подключаемым.

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

В Части 3 мы переключаем наш класс на «подключаемый» (то есть поддерживающий плагины, которые позволяют нам переопределять методы и свойства класса). Так, например, если у вас есть веб-приложение, у вас может быть реестр плагинов, и вы можете активировать их здесь. Обратите внимание также на функцию Dog_bark_beforeEvent(). Если я установлю $mixed = 'BLOCK_EVENT' перед оператором return, он заблокирует лай собаки, а также заблокирует событие Dog_bark_afterEvent, потому что не будет никакого события.

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

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

Разве это не декоратор?

MV. 07.11.2013 16:29

Я читайте об этом в Википедии и, эй, ты прав! :)

Volomike 08.11.2013 23:07

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

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

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

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

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

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

Это больше похоже на «push API», чем на «подключаемую» систему - вы предоставляете другим службам возможность получать уведомления о выбранных событиях. Под «плагинами» обычно подразумевается то, что вы можете установить приложение, а затем добавить функциональность для настройки его поведения в соответствии с вашими целями, что требует, чтобы плагин работал локально или, по крайней мере, имел безопасную и эффективную двустороннюю связь для обеспечения информация к приложение не просто принимает его из это. Эти две функции несколько отличаются, и во многих случаях «канал» (например, RSS, iCal) является простой альтернативой push API.

IMSoP 14.06.2013 22:47

Другие вопросы по теме