Skip to main content

Система плагинов 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' только одна будет рассматриваться как активная. Выбор активной имплиментации плагина происходит в соответствии с алгоритмом определения приоритета плагина.

Преимущества, которые даёт использование подобной плагинной архитектуры:

  1. Структуризация кода, выделение главных подсистем и их API, организация взаимодействия подсистем
  2. Спецификация единых подходов при реализации однотипных задач
  3. Инкапсуляция кода, а также сопутствующих ресурсов в виде фукнциональной единицы (черный ящик) с собственным семантичным именем и четко определенным API.
  4. Автоматическое встраивание новых функциональных единиц в подходящую управляющую систему.
  5. Единый стандарт к разработке и архитектуре приложения
  6. Повторное использование решений
  7. Выработка четкого и лаконичного "словаря" приложения
  8. Дополнительные возможности по оптимизации, мониторингу и отладке кода

Встроенные типы плагинов

В системе уже реализованы несколько типов плагинов, которые доступны сразу после установки XSyst и которые по сути являются сердцем системы.

Данные типы плагинов являются основой для целых подсистем платформы XSyst. Это отражено в таблице ниже. Все эти типы реализованы в модуле system.

Список встроенных типов плагинов:

НазваниеПодсистемаОписание
widgetWidgetApiСистема компонентов UI (виджетов), используемыех для формирования пользовательского интерфейса
routerRouterApiСистема роутинга (маршрутизации)
layoutLayoutApiСистема сеток позволяет управлять расположением виджетов на странице или в любом другом контейнере
clientClientApiСистема для создания именованных наборов ресурсов (js, css, fonts), необходимых на стороне клиента
EntityEntityApiВнутренняя ORM для управления сущностями бизнес-логики сайта

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

Список дополнительных типов плагинов:

НазваниеМодульОписание
xtable_actionxtableОпределение групповых операций для компонента Datagrid
xtable_cellxtableОпределение ячеек для компонента Datagrid
XgiftsServicexgiftsОпределение новых типов подарков, предоставляемых модулем xgifts
SearchProviderSearchОпределение новых типов провайдеров контента для модуля Search

Реализация своего типа плагинов

Система PluginApi позволяет легко добавлять новые типы плагинов.

Для реализации нового типа плагинов необходимо:

  1. С помощью хука 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'],
    ];
    }
  2. В модуле создать файл: <module>/src/PluginType/<PluginType>/<PluginType>PluginRegistry.php

    Пример: system/src/PluginType/Entity/EntityPluginRegistry.php

  3. В этом файле определёям класс <PluginType>PluginRegistry, унаследованный от PluginRegistryBase

    namespace modules\<module>\PluginType\<PluginType>;

    class <PluginType>PluginRegistry extends PluginRegistryBase {

    }

    Пример:

    namespace modules\system\PluginType\Entity;

    class EntityPluginRegistry extends PluginRegistryBase {

    }

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

  4. После этого можно создавать и использовать плагины данного типа.

Создание плагинов

Плагины могут быть определены либо в модулях, либо в темах, либо и в модулях и темах. Это зависит от параметра scope в объявлении соответствующего типа плагинов.

Если scope равен 'modules', значит плагины данного типа можно объявлять только в модулях. Если scope равен 'themes', значит плагины данного типа можно объявлять только в темах. Если scope пустой или 'all', значит плагины данного типа можно объявлять как в модулях, так в темах.

Способ 1

Термин

Это способ по умолчанию. На уровне файловой системы плагин представляет собой папку с файлами. Поэтому этот способ называется Плагинная папка.

Для создания плагина типа PluginType в модуле (или в теме, если данный тип плагина поддерживает) минимально необходимо:

  1. Создать директорию src/Plugins/{PluginType}/{PluginName}

  2. Создать в этой директории файл вида {PluginName}.info.inc Содержимое этого файла будет зависеть от требований к info.inc файлам данного типа плагинов.

  3. Создать все необходимые плагину файлы и ресурсы в рамках папки плагина или в его подпапках.

Способ 2

Термин

Это дополнительный способ создания плагинов. Иногда этот способ бывает удобнее, поскольку число файлов требуется меньше. На уровне файловой системы плагин, как правило, представляет файл с определённым в нём классом и методом, реализующим логику работы плагина.Поэтому этот способ называется Плагинный метод.

Некоторые типы плагинов могут поддерживать облегчённый способ определния плигинов с помощью классов группы плагинов. Чаще всего такие классы называются Plugins, но это не обязательно - класс может иметь любое название. Кроме того, таких классов может быть несколько. Главным требованием к таким классам явлется наличие метода pluginsInfo, возвращающего массив определений всех плагинов, реализованных в рамках класса. Логика каждого плагина, объявленного в данном классе, реализуется в виде метода класса (для более сложных случаев - в виде нескольких методов), включающего в своём названии название плагина.

Рассмотрим пример создания плигинов типа xtable_action в модуле user.

Пример плагинного класса для определения плагинов типа xtable_action:

src/Plugins/xtable_action/Plugins.inc
  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.

Использование плагинов

Процесс использования плагинов следующий:

  1. Получать ссылку на синглетон контроллера данного типа плагинов с помощью xlib({PluginRegistryClass}). $entityApi=xlib(EntityPluginRegistry::class);

    Пример:

    $entityApi = xlib(EntityPluginRegistry::class);
    $user = $entityApi->getRepository(UserEntity::class)->find($uid);
  2. Вызвать методы, предоставляемые 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);