Начиная с PHP 8.1, язык PHP имеет встроенную поддержку атрибутов, совместимых со сценарием использования системы плагинов Drupal. В результате Drupal перейдет от использования аннотаций к атрибутам PHP для предоставления метаданных и конфигурации плагинов. Это потребует от разработчиков изучения нового синтаксиса атрибутов PHP и обновления существующего кода для его использования. На данный момент Drupal продолжит поддерживать как аннотации, так и атрибуты. Но время идет и пора учится новому.
Давайте посмотрим, как мы к этому пришли и что вам нужно сделать, чтобы обновить свой код для будущих версий Drupal.
Плагины в Drupal
С момента выпуска Drupal на ООП система плагинов использовала аннотации, позволяющие собирать метаданные и конфигурацию экземпляра плагина. Менеджеры плагинов используют эти данные для таких действий, как составление списка плагинов заданного типа или эффективное получение удобочитаемой метки плагина. Система поддерживает и другие механизмы обнаружения, но аннотации используются наиболее часто, и с ними разработчики модулей чаще всего сталкиваются при создании собственного кода.
Аннотации хороши тем, что метаданные находятся в том же файле, что и класс PHP, реализующий плагин, что упрощает их обнаружение и улучшает общий опыт разработки. Аннотации — обычная функция во многих языках программирования. Но до версии PHP 8.0 они не поддерживались в PHP. Поэтому Drupal (и многие другие проекты PHP) используют популярную библиотеку doctrine/annotation в качестве прокладки.
Атрибуты были впервые добавлены в PHP в версии 8.0, но первоначальная версия не поддерживала вложенные атрибуты. То, что жизненно важно для того, чтобы они работали на Drupal. Эта функция была позже добавлена в PHP 8.1, что позволило Drupal перейти от использования аннотаций к использованию собственных атрибутов PHP для достижения тех же целей.
По мере развития языка PHP должен развиваться и Drupal.
Атрибуты и аннотации
Возможность добавлять метаданные о коде вместе с кодом — общая потребность. Настолько, что, когда PHP не поддерживал его изначально, возникли решения проблемы в пользовательском пространстве. Пакет doctrine/annotations является наиболее широко используемым решением. И он назвал эту функцию annotations.
Популярность пользовательских аннотаций привела к введению нескольких RFC (Attributes, Attributes V2) и возможному внедрению этой функции в ядро PHP. Чтобы свести к минимуму путаницу с реализацией на уровне пользователя, языковая функция называется attributes, а не аннотациями.
Некоторые заметные различия между атрибутами и аннотациями:
- Аннотации интерпретируются во время выполнения. Дополнительный шаг синтаксического анализа добавляет накладные расходы, которые могут повлиять на производительность. Особенно если аннотаций много. Хотя Drupal в основном смягчает это, кэшируя процесс обнаружения плагинов. Атрибуты, с другой стороны, разрешаются во время компиляции, а не во время выполнения. И являются родной особенностью языка. Это может привести к повышению производительности, особенно в средах, где используется opcache.
- Поскольку аннотации представляют собой своего рода хак поверх синтаксиса phpdoc, они загромождают комментарии (которые должны быть предназначены для людей) метаданными.
- Аннотации склонны к ошибкам из-за мелких нюансов. Например, аннотация должна располагаться последней в комментарии докблока. Если после аннотации появится тег phpdoc типа @see, парсер запутается.
- Поддержка IDE функций родного языка будет более распространенной.
Все еще не убеждены? Разработчики doctrine/annotations библиотеки аннотаций PHP считают, что собственные атрибуты PHP настолько лучше, чем текущие системы аннотаций, что библиотека устарела и больше не поддерживается активно. Это также является одной из причин, почему это изменение важно для Drupal. Мы не хотим полагаться на устаревший, неподдерживаемый код.
Атрибуты для разработчиков Drupal
С точки зрения разработчика Drupal, атрибуты преследуют те же цели и используются так же, как и аннотации, хотя синтаксис у них другой. Давайте посмотрим на несколько примеров.
Ниже приведен пример аннотации, используемой в Drupal 10.1 для определения плагина блока брендинга системы:
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\BlockBase; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Предоставляет блок для отображения элементов "Site branding".
*
* @Block(
* id = "system_branding_block",
* admin_label = @Translation("Site branding"),
* forms = {
* "settings_tray" = "Drupal\system\Form\SystemBrandingOffCanvasForm",
* },
* )
*/ class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInterface {}
Вот то же определение плагина блока из Drupal 10.2 с использованием атрибутов:
<?php
namespace Drupal\system\Plugin\Block;
use Drupal\Core\Block\Attribute\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Cache\Cache; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Url; use Drupal\system\Form\SystemBrandingOffCanvasForm; use Symfony\Component\DependencyInjection\ContainerInterface;
/**
*Предоставляет блок для отображения элементов "Site branding".
*/ #[Block( id: "system_branding_block", admin_label: new TranslatableMarkup("Site branding"), forms: ['settings_tray' => SystemBrandingOffCanvasForm::class] )] class SystemBrandingBlock extends BlockBase implements ContainerFactoryPluginInterface {}
На первый взгляд мы можем увидеть пару ключевых синтаксических отличий. Использование для объявления атрибута вместо (разделителя докблока), используемого для аннотаций. Это ясно дает понять, что метаданные и комментарий над ними — это две разные вещи. Мне нравится это в атрибутах. В моей IDE я даже использую подсветку синтаксиса атрибутов.#[ ... ]/** ... */
Я включил инструкции use в свои примеры до и после, потому что важное различие между аннотациями и атрибутами заключается в том, что вы должны включить инструкцию use для класса attributes, например, используйте Drupal\Core\Block\Attribute\Block;. Вам также необходимо включить инструкции use для любых других классов, которые вы используете, в объявление атрибута, например, use Drupal\Core\StringTranslation\TranslatableMarkup;. Система аннотаций не имеет такого требования. Немного больше работы, но у этого также есть преимущество в том, что становится совершенно ясно, какой класс аннотаций используется, и я могу использовать встроенные в мою IDE инструменты навигации по коду, чтобы быстро найти класс и его
Аргументами атрибутов могут быть только литеральные значения или константные выражения (значения, которые можно вычислить во время компиляции). Подробнее о синтаксисе можно прочитать в официальной документации.
Начиная с версии PHP 8.0 поддерживает именованные параметры. Вот почему передача аргументов конструктору атрибутов типа id: "system_branding_block" работает. И позволяет передавать аргументы в любом порядке.
Определение пользовательских типов атрибутов
В Drupal каждый тип плагина будет иметь свой собственный тип атрибута. Как разработчик, вы столкнетесь с ними в 2 сценариях:
- Вам нужно реализовать плагин определенного типа, который требует объявления правильного атрибута;
- Вы создаете новый тип плагина и связанный с ним менеджер плагинов, и вам нужно будет определить тип атрибута и использовать его в менеджере плагинов.
Атрибуты извлекаются с помощью PHP Reflection API. Во время компиляции атрибуты анализируются, и их данные сохраняются как внутренние структуры. Это может быть извлечено с помощью ReflectionClass::GetAttributes() и, по сути, сводится к массиву ReflectionAttributeitems. Вы можете думать об этом как о массиве, содержащем данные из объявления атрибута. Менеджер плагинов Drupal может использовать эту информацию для получения идентификатора плагина или метки.
Атрибуты связаны с классами и тем, что они содержат. И так же, как и аннотации, должны быть задокументированы в этом классе. Поэтому, когда вы будете готовы реализовать плагин, вам сначала нужно будет выяснить, какой атрибут использовать, и какой класс содержит документацию. Как и в текущей системе, это будет проще всего выяснить, посмотрев на существующую реализацию плагина и выучив оттуда имя атрибута.
Когда дело доходит до преобразования аннотаций в атрибуты, я бы предположил, что в большинстве случаев имя останется прежним. Например, @Block() становится #[Block()], а @ContentEntity() становится #[ContentEntity()].
Ниже приведен код для Drupal\Core\Block\Attribute\Block. Какой атрибут будет использоваться для определения плагина Block.
<?php
namespace Drupal\Core\Block\Attribute;
use Drupal\Component\Plugin\Attribute\Plugin;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Атрибут блока.
*/ #[\Attribute(\Attribute::TARGET_CLASS)] class Block extends Plugin {
/**
* Создает атрибут блока.
*
* @param string $id
* Идентификатор плагина.
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $admin_label
* Административная метка блока.
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $category
* (необязательно) Категория в пользовательском интерфейсе
* администратора, в которой будет указан блок.
* @param \Drupal\Core\Annotation\ContextDefinition[] $context_definitions
* (необязательно) Массив определений контекста, описывающий
* контекст, используемый плагином. Массив определяется именами
* контекстов.
* @param string|null $deriver
* (необязательно) Класс получатель.
* @param string[] $forms
* (необязательно) Массив имен классов форм, обозначаемых строкой.
*/
public function __construct(
public readonly string $id,
public readonly ?TranslatableMarkup $admin_label = NULL,
public readonly ?TranslatableMarkup $category = NULL,
public readonly array $context_definitions = [],
public readonly ?string $deriver = NULL,
public readonly array $forms = [] ) {}
}
Есть несколько вещей, которые выделялись для меня, когда я впервые прочитал этот код:
#[\Attribute(\Attribute::TARGET_CLASS)]: В этой строке фактически говорится, что класс Block определяет атрибут #[Block()] и что атрибут block может использоваться только в классах. Или, другими словами, код, который комментирует атрибут #[Block()], должен быть классом, иначе он не будет проверяться.
Комментарий docblock для метода __construct() документирует параметры атрибута. В нем объясняются все необходимые и необязательные входные данные для атрибута.
Использование атрибута аналогично созданию экземпляра объекта с использованием ключевого слова new. Это имя класса, Block, с парой круглых скобок (), которые содержат любые аргументы для конструктора класса. Используя эти знания, я могу видеть из приведенного выше кода, что атрибут #[Block()] требует параметра id и будет принимать необязательные аргументы admin_label, category, context_definitions, deriver и forms. Мне также нравится, что это дает понять, что аргумент admin_label ожидает объект TranslatableMarkup в качестве своего значения, и что forms должен быть массивом.
Однако я бы хотел, чтобы документация по forms была немного более подробной. Мне непонятно, как кто-то мог узнать, что forms['settings_tray'] является полезным ключом, а forms['cool_form_one'] - нет. Хотя, по крайней мере, это ничуть не хуже, чем было с аннотациями.
Обновление менеджера плагинов для использования нового типа атрибута
Чтобы обновить менеджер плагинов для использования нового типа атрибута, сначала введите объект атрибута, создав новый класс. Класс атрибутов будет сопоставлять ключи, используемые в аннотации, с именами параметров в атрибуте. Это не строгое требование, но это значительно облегчит всем, кто внедряет ваш тип плагина, обновление своих экземпляров плагинов.
Пока не удаляйте старый класс аннотаций. На данный момент Drupal будет поддерживать как аннотации, так и атрибуты для обнаружения.
Большинство менеджеров плагинов расширяют \Drupal\Core\Plugin\DefaultPluginManager и переопределяют метод __construct(). Новый конструктор вызовет конструктор родительского класса и при этом сообщит менеджеру плагинов, как обнаружить плагины данного типа и какой класс использовать как для атрибутов, так и для аннотаций. Вы захотите обновить свой переопределенный метод конструктора, чтобы он передавал класс атрибутов, который вы добавили выше, и класс аннотаций родительскому классу. DefaultPluginManager пока будет работать с обнаружением атрибутов и без него.
Пример до
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface $logger)
{
parent::__construct(
'Plugin/Block',
$namespaces,
$module_handler,
'Drupal\Core\Block\BlockPluginInterface',
Block::class,
'Drupal\Core\Block\Annotation\Block'
);
// Дополнительный код ...
}
Пример после
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface $logger)
{
parent::__construct(
'Plugin/Block',
$namespaces,
$module_handler,
'Drupal\Core\Block\BlockPluginInterface', 'Drupal\Core\Block\Annotation\Block'
);
// Дополнительный код ...
}
После внесения этого изменения ваш менеджер плагинов будет поддерживать как аннотации, так и атрибуты для обнаружения. Как только появится более конкретный план по отказу от аннотаций, я ожидаю, что при необходимости появятся и дополнительные указания о том, как справиться с этим в коде вашего собственного менеджера плагинов.
Если вы действительно хотите заняться этим, посмотрите \Drupal\Core\Plugin\Discovery\AttributeDiscoveryWithAnnotations.
Когда я должен начать использовать атрибуты вместо аннотаций?
Вам, наверное интересно узнать ответ на вопрос, могу ли я использовать атрибуты прямо сейчас? Должен ли я использовать атрибуты прямо сейчас?
Лично я подожду, пока поддержка ректора Drupal не будет завершена и долгосрочные планы устаревания не будут улажены, прежде чем я начну преобразовывать любой из своих проектов.
Тем не менее, если вы хотите принять участие в преобразовании типов основных плагинов Drupal для использования обнаружения на основе атрибуции, есть список проблем для преобразования обнаружения типов плагина в на основе атрибутов.
Изменение от аннотаций к атрибутам, хотя и относительно простое, пугает своим масштабом. Без сомнения, некоторые из вас захотят начать обновлять свой код прямо сейчас, а другие могут немного беспокоиться о том, что это будет означать для долгосрочного обслуживания.
Для атрибутов требуется Drupal 10.2.0+
Возможность использования атрибутов для плагинов была впервые представлена в Drupal 10.2.0. Как минимум, любой код, использующий атрибуты, должен быть в проекте Drupal 10.2+.
И в выпуске Drupal 10.2 только некоторые из основных типов плагинов будут преобразованы для использования атрибутов для обнаружения (блоки и действия). Это говорит о том, что, хотя вы должны быть в курсе атрибутов и того, как их использовать, вы на самом деле не сможете их использовать, пока не будут преобразованы все (или большинство) типов плагинов ядра.
Это займет время, и поскольку в цикле Drupal 10 это происходит относительно поздно, возможно, что отказ от аннотаций может не произойти до Drupal 11, а поддержка будет удалена в Drupal 12.
С другой стороны, doctrine/annotations уже устарели. И за время выпуска Drupal 12 это может быть EOL. Что поставило бы сообщество Drupal в зависимость, чтобы продолжать поддерживать библиотеку разбора аннотаций. Или, по крайней мере, его части, которые мы используем.
Моя интуиция говорит, что аннотации будут поддерживаться вместе с атрибутами в течение некоторого времени.
Скоро появится поддержка ректора Drupal
Он еще не завершен, но продолжается работа по обновлению проекта ректора Drupal, чтобы он мог автоматически преобразовывать аннотации Doctrine вашего кода в атрибуты PHP. Как только это будет завершено, вполне вероятно, что эту функциональность можно будет также встроить в Project Update Bot. Это поможет облегчить, хотя и не полностью, работу по обновлению.
В стандартах кодирования Drupal пока нет ничего об атрибутах. Хотя есть открытый вопрос: документ использует атрибуты PHP для плагинов, и я подозреваю, что в будущем мы получим некоторые стандарты форматирования.
В руководство по API плагинов на Drupal.org добавлена документация: Плагины на основе атрибутов .
Заключение
Drupal переходит от аннотаций к собственным атрибутам PHP для обнаружения и настройки плагинов.
Из этой статьи мы узнали, что:
- Атрибуты PHP — это собственная реализация решения той же проблемы, которую решают аннотации. Они быстрее, имеют лучший опыт разработки и в долгосрочной перспективе будут лучше поддерживаться.
- Как разработчику Drupal, вам необходимо научиться использовать атрибуты PHP для определения новых плагинов Drupal. Хотя переключатель требует изучения нового синтаксиса, общее применение остается аналогичным. Это должно быть знакомо всем, кто ранее использовал аннотации.
- Поддержка атрибутов PHP была добавлена в Drupal 10.2 и все еще развивается. На данный момент разработчики должны знать, что изменения грядут, но им не нужно сразу же пытаться преобразовать весь свой код. Вместо этого дождитесь более подробного плана прекращения поддержки и ректора Drupal, который предоставит поддержку для помощи с обновлениями.
Цель состоит в том, чтобы обеспечить плавный переход, который принесет пользу сообществу Drupal и сохранит совместимость Drupal с развивающимся языком PHP.