Евгений Никитин Евгений Никитин

Русский English

Шаблоны проектирования в Drupal 9

Drupal разработчики редко имеют дело с паттернами проектирования объектов, т.к. в основном им приходится работать в уже сформированной среде. Мы используем шаблоны каждый день, но даже не задумываемся над этим. Имея представление о паттернах вам будет легче общаться с коллегами - достаточно сказать название паттерна и все понимают о чем идет речь. Вместо изобретения очередного велосипеда вы можете использовать уже отработанные эффективные решения. Паттерны проектирования одни и те же в разных языках программирования, отличается только реализация. Так что зная шаблоны вы всегда сможете найти им применение в своей карьере. Кстати, вопрос о паттернах проектирования один из самых частых при собеседованиях.

Порождающие паттерны

Одиночка (Singleton)

“Одиночка” - это, наверное, самый известный архитектурный паттерн. Его назначение - это создавать единственный экземпляр класса и предоставлять единую точку доступа к объекту.

В Drupal с помощью шаблона “Одиночка” реализован доступ к настройками сайта. На схеме внизу изображено как это работает:

Шаблон Одиночка в Drupal

Во время начальной загрузки ядра Drupal инициализирует настройки используя Settings::initialize(). После этого экземпляр класса помещается в защищенную переменную self::$instance, откуда он берется в дальнейшем. Настройки сайта являются общим ресурсом, поэтому шаблон “Одиночка” позволяет Drupal обращаться к настройкам только один раз при инициализации.

Фабричный метод (Factory method)

“Фабричный метод” определяет общий интерфейс для создания объектов. Тип объекта, что будет создан, может быть изменен в зависимости от настроек.

В Drupal “Фабричный метод” используется для получения объекта для работы с кешем. Различные типы кеша требуют собственную реализацию, поэтому у нас есть классы реализующие интерфейс CacheBackendInterface такие как DatabaseBackend, PhpBackend, MemoryBackend и т.д. для работы с разными видами кеша. К тому же для разных подсистем кеш у нас может быть разный. Поэтому был определен интерфейс CacheFactoryInterface и его реализации DatabaseBackendFactory, PhpBackendFactory, MemoryBackendFactory и т.д., которые создают объекты для работы с кешем определенного типа. Для каждого класса, реализующий интерфейс, CacheBackendInterface у нас есть своя фабрика реализующая CacheFactoryInterface.

Какую конкретно использовать фабрику решается в классе \Drupal\Core\Cache\CacheFactory - в зависимости от настроек системы используется та или иная фабрика. Поэтому, например, чтобы получить объект для работы с кешем рендеринга вам нужно просто выполнить \Drupal::service(‘cache_factory’)->get(‘render’). Вам не нужно знать какой конкретно тип кеша используется в системе для рендеринга шаблонов. В settings.php вы можете выставить

settings['cache']['bins']['render'] = 'cache.backend.database';

или

settings['cache']['bins']['render'] = 'cache.backend.memcache';

и фабрика вернет вам объект класса DatabaseBackend либо MemcacheBackend в зависимости от настроек.

Также к преимуществам фабричного метода относится то, что он позволяет возвращать уже созданные объекты, а не создавать их каждый раз заново, что сэкономит вам ресурсы (пример \Drupal\Core\Cache\MemoryBackendFactory).

Шаблон Фабричный метод в Drupal

Внедрение зависимостей (Dependency injection)

“Внедрение зависимостей” - явная передача служб в объект используя конструктор или сеттеры. В Drupal мы определяем объекты-сервисы, ответственные за определенные функции. Когда нам нужны эти сервисы в наших классах мы:

  • описываем зависимости как аргументы при определении сервисов в *.services.yml. Эти зависимости будут переданы в __construct() метод.
  • используем интерфейс ContainerFactoryPluginInterface для плагинов.
  • используем интерфейс ContainerInjectionInterface для остальных объектов (но не везде, например Entity не поддерживают внедрение зависимостей).

Пример работы ввнедрения зависимостей в Drupal

На схеме описано как сервисы “config_factory”, “alias_manager”, “path_validator”, “request_context” добавляются в форму SiteInformationForm. При создании формы мы их получаем в конструкторе и сохраняем во внутренних переменных класса - зависимости внедрены.

Данный шаблон позволяет с легкостью распределять ответственность между классами. К тому же мы не зависим от конкретной реализации зависимости - нам нужно только знать какой интерфейс она имплементирует. Поэтому, в дальнейшем мы можем передать в объект другую реализацию зависимости без изменения самого объекта.

Поведенческие паттерны

Посредник (Intermediary, Controller, Mediator)

Шаблон “Посредник” используется для реализации взаимодействия между компонентами приложения.

Вы будете удивлены, но паттерн “Посредник” является основой для главной отличительной особенности Drupal - системы хуков. У нас есть основной элемент - посредник, который предоставляет единый интерфейс через который общаются разные части системы. Компоненты-получатели не знают кто отправил запрос, а компоненты-отправители не знают кто обработает запрос.

На схеме внизу представлено как работают хуки. В нашем примере, когда компонент-отправитель EntityStorageBase хочет уведомить о каком-либо событии (это может быть: presave, create, insert, update, delete, revision_delete, predelete) произошедшим с Entity, он вызывает посредник ModuleHandler и передает ему название события/хука и параметры. ModuleHandler содержит в себе ссылки на все хуки в системе в параметре implementations. Затем он вызывает соответствующие методы-хуки c передаными параметрами.

Схема работы хук в шаблоне Посредник

Также паттерн “Посредник” используется в системе событий, которая в Drupal построена на основе Symfony EventDispatcher. Здесь посредником является ContainerAwareEventDispatcher. Компоненты-отправители (в нашем примере это \Drupal\Core\Config) уведомляют посредник о наступлении события. В переменной listeners в посреднике находятся ссылки на все компоненты-подписчики (реализуют EventSubscriberInterface). Когда посредник получает уведомление о событии он вызывает подписчики, которые подписаны на вызываемое событие.

Система событий Drupal в шаблоне Посредник

Посетитель (Visitor)

Шаблон “Посетитель” используется для того чтобы отделить операции над объектом от самого объекта. Таким образом, не затрагивая структуру объекта мы можем добавить в программу какие-либо действия с ним.

Шаблон “Посетитель” мы можем найти в Twig в парсинге шаблонов - строится дерево элементов (html тегов) шаблона и затем, при обходе дерева, эти элементы обрабатываются объектами-посетителями.

Шаблон Посетитель в Drupal

Как вы видите на схеме в Twig используются разные объекты-посетители (EscaperNodeVisitor, TwigNodeVisitor, SelfAnalysisNodeVisitor). Если нам нужно будет обработать элементы как-то еще, то мы можем добавить дополнительный посетитель без изменения класса элемента.

Если разные элементы должны быть обработаны по-разному, то посетитель реализует у себя эти варианты (посмотрите на пример SelfAnalysisNodeVisitor - для каждого элемента своя реализация алгоритма). Данный шаблон хорошо подходит если нам редко приходится изменять элементы над которыми мы совершаем операции. При изменении элемента может потребоваться внести изменения в реализацию посетителей.

Структурные паттерны

Заместитель (Proxy)

Шаблон “Заместитель” применяется когда нужно использовать вместо реальных объектов объекты-заместители. Эти объекты позволят выполнять код до или после вызова методов оригинального объекта, или управлять доступом к оригинальному объекту.

В Drupal паттерн “Заместитель” реализован в классе \Drupal\Core\Session\AccountProxy сервиса “current_user”. AccountProxy реализует все методы класса \Drupal\Core\Session\UserSession чьим заместителем он является. Таким образом мы можем работать с объектом класса AccountProxy также как с объектом класса UserSession. Данный подход позволяет устанавливать и менять текущего пользователя без повторной инициализации сервиса “current_user”. К тому же, этот сервис используется всеми сервисами как единая точка доступа к сессии текущего пользователя.

Шаблон Заместитель в Drupal

Ссылки