Перейти к основному содержимому

Система плагинов 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);