Система плагинов XSyst
Система плагинов XSyst позволяет расширять функциональность сайта не теряя контроля на програмным кодом, не ухудшая производительность сайта и сохраняя время разработчиков.
Описание
Система плагинов является концептуальной и фундаментальной идеей всей архитектуры XSyst.
Именно за счёт неё XSyst является гибкой, расширяемой и при этом простой системой. Именно поэтому большая часть файлов любого сайта на базе XSyst связана с плагинами.
Плагин - это именованная инкапсуляция программного кода и сопутствующих ресурсов (например, js,css,image файлы),
взаимодействующая с другими частями программной среды посредством API, предоставляемого контроллером данного типа плагинов.
Контоллер плагинов управляет плагинами только определенного типа и представляет собой PHP-класс, унаследованный от базового класса PluginRegistryBase и выполняет как минимум следующие функции:
- обнаружение новых плагинов в системе и добавление информации о них в регистр плагинов
- разрешение конфликтных ситуаций, когда имеется несколько плагинов с одинаковым именем
- предоставление API для взаимодействия других частей системы с плагинами
- подключение к некоторым хукам системы
Доступ к тем или иным возможностям или ресурсам плагина возможен только посредством API контроллера плагинов данного типа.
Доступ к контроллеру плагинов осуществляется с помощью Service-локатора xlib().
Примеры:
$entityApi = xlib(EntityPluginRegistry::class);
$widgetApi = xlib(WidgetPluginRegistry::class);
$clientApi = xlib(ClientPluginRegistry::class);
$layoutApi = xlib(LayoutPluginRegistry::class);
$contextApi = xlib(ContextPluginRegistry::class);
$xgiftsServiceApi = xlib(XgiftsServicePluginRegistry::class);
$xtableActionApi = xlib(XtableActionPluginRegistry::class);
$xtableCellApi = xlib(XtableCellPluginRegistry::class);
Важнейшей особенностью любого контроллера плагинов является использование регистра плагинов.
Регистр плагинов представляет собой хранилище информации по всем плагинам данного типа.
Доступ к регистру плагинов определенного типа осуществляется посредством метода getRegistry(),
доступного в любом контроллере плагинов.
Регистр плагинов всегда кэшируется (поэтому при любом изменении состава плагинов необходимо сбрасывать кэш). Для каждого типа плагинов создаётся свой регистр.
При отсутствии регистра в кэше запускается его автоматическое построение регистра и сохранение его в кэше (в момент первого обращения к плагинам данного типа).
Любой контроллер плагинов имеет свою специфику в соответствии с назначением плагинов его типа.
Структура плагина соответствующим образом зависит от его назначения. Поэтому плагины (даже одного типа) могут сильно отличаться
по структуре друг от друга. Тем не менее, с точки зрения файловой системы плагин представляет собой директорию вида
{module_or_theme}/src/Plugins/{plugin_type}/{plugin_name}, содержащую файл вида {plugin_name}.info.inc с информацией о плагине и его базовыми параметрами. Также в этой папке могут находиться любые другие файлы, необходимые для работы данного плагина (например, tpl.php, inc, css, js файлы).
Контроллер определенного типа плагинов и совокупность плагинов данного типа можно рассматривать как подсистему, а публичное API контроллера, в данном случае, как API данной подсистемы для взаимодействия с другими частями программной среды.
Примеры:
### Пример 1:
- Тип плагинов: client
- Название управляющей системы: client (Клиентские плагины)
- API-класс управляющей системы: ClientPluginRegistry
- Примеры плагинов типа client: xpopup, xtip
- Пример вз-я с управляющей системой: xlib(ClientPluginRegistry::class)->require('xpopup')
### Пример 2:
- Тип плагинов: Entity
- Название управляющей системы: Entity (Сущности для моделирования бизнес-логики и сохранения состояния бизнес-объектов в БД)
- API-класс управляющей системы: EntityPluginRegistry
- Примеры плагинов типа Entity: User, Node, Comment
- Пример вз-я с управляющей системой:
$userRepo = xlib(EntityPluginRegistry::class)->getRepository(UserEntity::class);
или тоже самое с помощью фасада EM:
$userRepo = EM::getRepository(UserEntity::class);
Плагины могут зависеть от других плагинов такого же типа (а также и от плагинов других типов).
Плагины могут быть группирующими (т.е. выполнять группировку других плагинов)
Имя плагина - его уникальный идентификатор в рамках множества плагинов данного типа. Это значит, что, например, нельзя иметь 2 разных виджета 'username' в системе. При наличии нескольких реализаций 'username' только одна будет рассматриваться как активная. Выбор активной имплиментации плагина происходит в соответствии с алгоритмом определения приоритета плагина.
Преимущества, которые даёт использование подобной плагинной архитектуры:
- Структуризация кода, выделение главных подсистем и их API, организация взаимодействия подсистем
- Спецификация единых подходов при реализации однотипных задач
- Инкапсуляция кода, а также сопутствующих ресурсов в виде фукнциональной единицы (черный ящик) с собственным семантичным именем и четко определенным API.
- Автоматическое встраивание новых функциональных единиц в подходящую управляющую систему.
- Единый стандарт к разработке и архитектуре приложения
- Повторное использование решений
- Выработка четкого и лаконичного "словаря" приложения
- Дополнительные возможности по оптимизации, мониторингу и отладке кода
Встроенные типы плагинов
В системе уже реализованы несколько типов плагинов, которые доступны сразу после установки XSyst и которые по сути являются сердцем системы.
Данные типы плагинов являются основой для целых подсистем платформы XSyst. Это отражено в таблице ниже. Все эти типы реализованы в модуле system.
Список встроенных типов плагинов:
| Название | Подсистема | Описание |
|---|---|---|
| widget | WidgetApi | Система компонентов UI (виджетов), используемыех для формирования пользовательского интерфейса |
| router | RouterApi | Система роутинга (маршрутизации) |
| layout | LayoutApi | Система сеток позволяет управлять расположением виджетов на странице или в любом другом контейнере |
| client | ClientApi | Система для создания именованных наборов ресурсов (js, css, fonts), необходимых на стороне клиента |
| Entity | EntityApi | Внутренняя ORM для управления сущностями бизнес-логики сайта |
Кроме этих системных типов плагинов есть также ряд дополнительных типов плагинов, которые реализованы в подключаемых модулях.
Список дополнительных типов плагинов:
| Название | Модуль | Описание |
|---|---|---|
| xtable_action | xtable | Определение групповых операций для компонента Datagrid |
| xtable_cell | xtable | Определение ячеек для компонента Datagrid |
| XgiftsService | xgifts | Определение новых типов подарков, предоставляемых модулем xgifts |
| SearchProvider | Search | Определение новых типов провайдеров контента для модуля Search |
Реализация своего типа плагинов
Система PluginApi позволяет легко добавлять новые типы плагинов.
Для реализации нового типа плагинов необходимо:
С помощью хука
hook_pluginapi_typesобъявить новый тип плагинов://Например:
function system_pluginapi_types()
{
return [
'widget' => [
//В случае нахождения нескольких имплиментаций одного плагина добавлять в конец очереди.
//В этом случае активная имплиментация будет получаться в виде слияения всех имплиментаций с учётом приоритетности
//По умолчанию: true
'multiple' => true,
//В случае нахождения нескольких имплиментаций одного плагина перезаписывать последней
//По умолчанию: false
'override' => false,
//Поддерживает ли данный тип плагинов имплиментацию хука hook_element()
//По умолчанию: false
'hook_element' => true,
//Если true, то поиск папок плагинов будет производится рекурсивно согласно правилам БЭМ
//По умолчанию: false
'bem_recursive' => true,
//Где могут располагаться плагины данного типа ('all' - везде, 'modules' - только в модулях, 'themes' - только в темах)
//По умолчанию: 'all'
'scope' => 'all',
],
'client' => ['multiple' => true, 'override' => false,],
'router' => ['multiple' => true, 'override' => false, 'scope' => 'modules',],
'layout' => ['multiple' => true, 'override' => false, 'bem_recursive' => true],
'Entity' => ['multiple' => true, 'override' => false, 'scope' => 'modules', 'bem_recursive' => true],
'EntityExt' => ['multiple' => true, 'override' => false, 'scope' => 'modules', 'bem_recursive' => true],
'context' => ['override' => true, 'scope' => 'modules'],
];
}В модуле создать файл:
<module>/src/PluginType/<PluginType>/<PluginType>PluginRegistry.phpПример:
system/src/PluginType/Entity/EntityPluginRegistry.phpВ этом файле определёям класс
<PluginType>PluginRegistry, унаследованный отPluginRegistryBasenamespace modules\<module>\PluginType\<PluginType>;
class <PluginType>PluginRegistry extends PluginRegistryBase {
}Пример:
namespace modules\system\PluginType\Entity;
class EntityPluginRegistry extends PluginRegistryBase {
}В этом классе реализовать бизнес-логику работы данного типа плагинов, а также его публичное API, с помощью которого будет производиться взаимодействие других частей системы с плагинами данного типа.
После этого можно создавать и использовать плагины данного типа.
Создание плагинов
Плагины могут быть определены либо в модулях, либо в темах, либо и в модулях и темах.
Это зависит от параметра scope в объявлении соответствующего типа плагинов.
Если scope равен 'modules', значит плагины данного типа можно объявлять только в модулях. Если scope равен 'themes', значит плагины данного типа можно объявлять только в темах. Если scope пустой или 'all', значит плагины данного типа можно объявлять как в модулях, так в темах.
Способ 1
Это способ по умолчанию. На уровне файловой системы плагин представляет собой папку с файлами. Поэтому этот способ называется Плагинная папка.
Для создания плагина типа PluginType в модуле (или в теме, если данный тип плагина поддерживает) минимально необходимо:
Создать директорию
src/Plugins/{PluginType}/{PluginName}Создать в этой директории файл вида
{PluginName}.info.incСодержимое этого файла будет зависеть от требований к info.inc файлам данного типа плагинов.Создать все необходимые плагину файлы и ресурсы в рамках папки плагина или в его подпапках.
Способ 2
Это дополнительный способ создания плагинов. Иногда этот способ бывает удобнее, поскольку число файлов требуется меньше. На уровне файловой системы плагин, как правило, представляет файл с определённым в нём классом и методом, реализующим логику работы плагина.Поэтому этот способ называется Плагинный метод.
Некоторые типы плагинов могут поддерживать облегчённый способ определния плигинов с помощью классов группы плагинов.
Чаще всего такие классы называются Plugins, но это не обязательно - класс может иметь любое название.
Кроме того, таких классов может быть несколько. Главным требованием к таким классам явлется наличие метода
pluginsInfo, возвращающего массив определений всех плагинов, реализованных в рамках класса.
Логика каждого плагина, объявленного в данном классе, реализуется в виде метода класса (для более сложных случаев - в виде нескольких методов), включающего в своём названии название плагина.
Рассмотрим пример создания плигинов типа xtable_action в модуле user.
Пример плагинного класса для определения плагинов типа xtable_action:
namespace modules\user\Plugins\xtable_action;
class Plugins {
//Обязательный метод, в котором объявляем все плагины, которые будут реализованы в данном классе.
//Состав и структура данных для каждого плагина зависит от требований контроллера данного типа плагинов.
public function pluginsInfo()
{
$actions = [];
$actions['userExportData'] = [
'name' => 'Экспорт данных из таблицы users',
'description' => 'Экспорт данных из таблицы users в формат CSV',
'access' => 'administer users',
];
return $actions;
}
public function userExportData_submit(&$form, &$form_state)
{
//реализация логики работы плагина userExportData
}
}
Примеры использования сразу нескольких плагинных классов для объявления роутов модуля можно посмотреть в модуле liteforum.
Пример использования плагинного класса modules\user\Plugins\widget\SmallWidgets для реализации виджетов можно посмотреть в модуле user.
Использование плагинов
Процесс использования плагинов следующий:
Получать ссылку на синглетон контроллера данного типа плагинов с помощью
xlib({PluginRegistryClass}).$entityApi=xlib(EntityPluginRegistry::class);Пример:
$entityApi = xlib(EntityPluginRegistry::class);
$user = $entityApi->getRepository(UserEntity::class)->find($uid);Вызвать методы, предоставляемые API данного типа плагинов
xlib(PluginRegistryClass)->{someApiMethod}([args]);Пример:
//пример 1 - используем EntityPluginRegistry
$entityApi = xlib(EntityPluginRegistry::class);
$user = $entityApi->getRepository(UserEntity::class)->find($uid);
//пример 2 - используем ClientPluginRegistry
xlib(ClientPluginRegistry::class)->require('xpopup');
//пример 3 - используем XtableCellPluginRegistry
$xtableCell = xlib(XtableCellPluginRegistry::class)->get_cell('node_nid',$node);Это универсальный способ доступа к API плагинов любых типов.
В большинстве случаев, однако, есть более удобные фасады, декораторы или функции, чтобы работать с каждым из этих API.Пример:
//Вместо такой длинной записи для получения ссылки на репозиторий сущностей UserEntity
$userRepo = xlib(EntityPluginRegistry::class)->getRepository(UserEntity::class);
//лучше использовать фасад EM
$userRepo = EM::getRepository(UserEntity::class);
//Вместо такой длинной записи для получения ссылки на репозиторий сущностей UserEntity
$username = xlib(WidgetPluginRegistry::class)->renderWidget('username', $account->uid);
//лучше использовать helper-функцию widget():
$userRepo = widget('username', $account->uid);