Только для читателей Lifeexample возможно открыть интернет-магазин на Moguta.CMS со скидкой в 15%

<<< PHP время выполнения скрипта || 7 советов о том, как достичь своей цели >>>

PHP система плагинов на основе хуков (hooks)

07.12.2012
PHP система плагинов на основе хуков (hooks)

Здравствуйте, уважаемые читатели блога LifeExample, сегодня предлагаю затронуть очень редкую тему, информации о которой в интернете, кране не хватает. Говорить мы будем о том, как самостоятельно сделать систему плагинов для своей CMS.

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

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

Проработав некоторое время с движком WordPress, я не мог не оценить его механизм взаимодействия плагинов и ядра, который основан на системе хуков (Hooks – с англ. крючек, зацепка). Опираясь именно на эту концепцию системы плагинов, я решил разработать ее аналог в своей CMS.

PHP hooks – хуки и их концепция

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

Предлагаю рассмотреть, что такое php hooks, и как их реализовать на простом примере.

В упрощенном виде, система хуков в PHP может выглядеть таким образом:

  1. Имеется главный класс Main, в котором реализована вся логика приложения.
  2. Имеется файл plugin.php, содержащий обработчики событий, которые должны произойти классе Main. Например, событием может являться выполнение метода __construct().
  3. Также есть файл index.php, объединяющий эти два файла в одну систему.
  4. Запуская index.php инклудом подключается файл plugin.php, и создается экземпляр класса Main.
  5. В коде метода __construct() класса Main создаются хук (зацепка), для обработчика.
  6. В момент создания хука, выполняется тело функции из класса файла plugin.php, тем самым изменяя обычное создание экземпляра класса Main.

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

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

Как реализовать Hooks

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

Скачать пример реализации системы плагинов ( Скачали: 438 чел. ) 

В архиве находится три файла:

  • index.php – файл запускающий псевдо систему;
  • plugin.php – мини плагин;
  • libHook.php – библиотека для механизма хуков.

Давайте начнем разбираться с файла index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
//Флаг использования плагинов
$plugin = true;
//подключаем библиотеку для хуков
include 'libHooks.php';
// Инициализация менеджера плагинов, он будет следить за произходящими событиями
$PM = new PM();
//Подключение плагина
if($plugin) {
  include 'plugin.php';
}


//псевдо ядро системы
class Core{
  public function __construct(){
    echo "...Действия ядра...";
    // Используем глобальный менеджер плагинов
    global $PM;
    //Происходит событие demoHook, где-то в недрах ядра
    $PM->createHook('demoHook');
    echo "...Действия ядра...";
  }
}

//Эмуляция запуска ядра системы
$core = new Core();

В данном скрипте происходит:

  • подключение плагина;
  • подключение библиотеки хуков;
  • создание менеджера плагинов — экземпляр класса который предназначен для связи плагина с ядром систмы;
  • эмуляция запуска дейсвий ядра.

Обратите внимание на конструктор класса Core, в нем происходит инициализаци хука.

Следующая строка, создает действие с именем ‘demoHook’

1
$PM->createHook('demoHook');

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

Откроем из архива файл plugin.php

1
2
3
4
5
6
7
8
9
<?php

//пользовательская функция выполняющаяся при определенном событии
function myFunction(){
  echo "<br/> >>>Действия плагина<<< <br/>";
}

//регистрируем пользовательскую функцию как обработчика для события demoHook
$PM->registration(new EventHook('demoHook', 'myFunction'));

Этот файл представляет собой аналог плагина, в котором имеется функция myFunction(), назначением которой является расширение стандартного функционала системы.

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

Строка:

1
$PM->registration(new EventHook('demoHook', 'myFunction'));

Регистрирует нашу функцию в качестве обработчика, для события demoHook. Теперь когда бы не произошло событие demoHook всегда будет выполнено тело функции myFunction().

Запустив файлы архива, мы увидим такой результат:

Система hooks хуков

Как устроена система хуков

В выше приведенном примере использовалась библиотека для механизма крючков и зацепок, мы подключали ее строкой:

1
include 'libHooks.php';

Давайте откроем этот файл и посмотрим, что же в нем хранится, и как все это работает.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?php
/**
 * Библиотека хуков, содержит интерфейсы PluginManager и Hook,
 * а также класс PM и EventHook
 * Класс PM предназначен для управления плагинами
 * Класс Hook создает обработчик событий
 *
 * Автор: Авдеев Марк
 * Ссылка на описание: http://lifeexampl.nichost.ru/php-primeryi-skriptov/php-sistema-plaginov-na-osnove-hukov-hooks.html
 */


//Интерфейс для слушаемого
interface PluginManager {
  function registration(Hook $hook);
  function delete(Hook $hook);
  function createHook($hookName);
}

//Интерфейс для слушателя
interface Hook {
  function run(PluginManager $hookName);
}


// Клас PM (plugin Manager) управляет плагинами,
// регистрирует  плагины и устанавливает их взаимодействие с системой.
class PM implements PluginManager{

  // Заристрированные обработчики хуков
  private $_eventHook;

  public function __construct(){
    $this->_eventHook = array();
  }

  // регистрация обработчика для хука
  public function registration(Hook $eventHook){
    $this->_eventHook[] = $eventHook;
  }


  // удаление заданного обработчика
  public function delete(Hook $eventHook){
    if($id = array_search($eventHook, $this->_eventHook, TRUE)){
      unset($this->_eventHook[$id]);
    }
  }


  // Если в программе создан hook, сказать об этом всем
  // обработчикам, вдруг кто-то из них ждет этого
  // хука, для выполнения пользовательсякой функции.
  public function createHook($hookName){
    foreach($this->_eventHook as $eventHook){
      if($eventHook->getHookName() == $hookName){
        $eventHook->run($this);
      }
    }
  }

}

//Вешает обработчик для заданного хука
class EventHook implements Hook{

  // Наименование хука
  private $_hookName;
  // пользовательская функция, которая сработает при хуке
  private $_functionName;

  public function __construct($hookName, $functionName){
    $this->_hookName = $hookName;
    $this->_functionName = $functionName;
  }

  // Запуск обработчика для хука
  public function run(PluginManager $pm){
    if(function_exists($this->_functionName)){
      call_user_func($this->_functionName);
    }
  }

  public function getHookName(){
    return $this->_hookName;
  }

}

Вся логика взаимодействия хуков с обработчиками основана на паттерне observer, также известного как "Издатель и Подписчик". Я не буду сейчас описывать суть данного шаблона проектирования, поскольку делал это ранее.

Смысл заключается в том, что все пользовательские функции регистрируются для соответствующих действий классом PM в качестве обработчиков событий-хуков (EventHook).

Когда в системе происходит событие createHook("имя события — хука"), сгенерированное опять таки классом PM, то он как генератор имеет доступ к реестру пользовательских функций и начинает подбирать подходящую из них, для обработки текущего события.

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

Вывод

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

Ну а если вам все же интересно как можно развить данную заготовку до полноценной системы плагинов, рекомендую ознакомиться с моим движком MOGUTA.CMS, в котором механизм хуков учитывает все атрибуты настоящих плагинов такие как:

  • Регистрация плагина в системе;
  • Активация плагина;
  • Деактивация;
  • Изменение результата системной функции;
  • Изменение тела системной функции;
  • Подмена параметров системной функции.

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

Надеюсь эта реализация системы хуков оказалась для вас настолько же интересной как и для меня.

Чтобы не пропустить публикацию следующей статьи подписывайтесь на рассылку по E-mail или RSS ленту блога.

Нравится

Комментарии

  • Ого Спасибо !

  • Подскажите пожалуйста, вы тут использовали паттерн Фабрика ?

  • Влад

    Я вот смотрю на ваши статьи, и думаю, что у вас всё старомодно, не вызывающе, сделайте пару статей про автозагрузчик на SPL с применением логов и загрузкой карты подключения путей, пару статей про namespace, и одну большую статью про создание мощной модульной MVC системы с применеием автозагрузчика, и namespace… И так далее в том же духе можете писать статьи, пишите о новом, надоело читать одно и тоже старье. Хочется нового!!!

    • Спасибо, Влад. Обязательно сделаю статьи не старомодными)) Если вообще можно применить это слово к теоретическим материалам. Есть такой ресурс — хабрахабр, думаю там вы найдете много нового для себя.

  • Спасибо за статью, просто и информативно!

  • timur

    hookname.pre — Запускается перед функцией
    hookname.post- Запускается после функцией
    hookname.rewrite — Перезаписывает функцию
    Вот так грамотно..

  • Дмитрий

    Отличный пример. А как это всё в ООП засунуть?)

  • Оставить комментарий

    Подписаться на комментарии к этой статье по RSS

    Яндекс.Метрика