2008-08-01 4 views
244

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

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

ответ

149

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

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

+3

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

+19

Педантичное примечание: это не пример шаблона Observer. Это пример [`Mediator Pattern`] (http://sourcemaking.com/design_patterns/mediator). Истинные наблюдатели - это чистое уведомление, отсутствует передача сообщений или условное уведомление (а также нет центрального администратора для контроля уведомлений). Это не делает ответ * неправильным *, но следует отметить, что люди, вызывающие вещи, не могут ошибаться ... – ircmaxell 2011-02-09 23:35:07

+0

Обратите внимание, что при использовании нескольких крючков/слушателей вы должны возвращать либо строки, либо массивы, а не обе. Я реализовал нечто подобное для Hound CMS - https://getbutterfly.com/hound/. – Ciprian 2017-12-19 11:21:08

13

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

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

+0

Я бы добавил DokuWiki в список систем, на которые вы можете взглянуть. У этого есть хорошая система событий, которая позволяет богатую экосистему плагина. – chiborg 2010-08-03 13:53:15

31

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

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

+1 к kdeloach от меня.

12

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

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

9

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

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

18

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

?> 
49

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

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

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

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

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

6

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

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

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

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

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

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