Атрибуты Введение в атрибуты PHP-атрибуты — структурированные машиночитаемые метаданные для классов, методов, функций, параметров, свойств и констант. Атрибуты инспектируют при выполнении кода через API-интерфейс модуля Reflection, добиваясь динамического поведения без изменения кода. Атрибуты — декларативный способ аннотировать код метаданными. Атрибуты отделяют внутреннюю логику кода от контекста и условий применения этой логики. Интерфейсы жёстко задают структуру класса через обязательные методы, атрибуты же добавляют метаданные множеству элементов: методам, функциям, свойствам и константам. В отличие от интерфейсов, которые заставляют добавлять в класс обязательные методы, атрибуты аннотируют код и не требуют изменять структуру класса. Атрибуты умеют дополнять класс методами, которые не требует интерфейс, или заменять интерфейсные методы только за счёт добавления метаданных, без изменения структуры интерфейса. Рассмотрим интерфейс ActionHandler, который представляет операцию в приложении. Одним реализациям требуется этап настройки, а другим — нет. Не станем заставлять каждый класс, в котором реализуется интерфейс ActionHandler, определять метод setUp(), а укажем методы, которые требуется выполнить для настройки, атрибутом. Такой подход увеличивает гибкость и разрешает применять атрибуты многократно, когда требуется. Пример добавления в класс дополнительных неинтерфейсных методов через атрибуты fileName)) { throw new RuntimeException("Файл не существует"); } } #[SetUp] public function targetDirectoryExists() { if (!file_exists($this->targetDirectory)) { mkdir($this->targetDirectory); } elseif (!is_dir($this->targetDirectory)) { throw new RuntimeException("Целевой путь $this->targetDirectory не указывает на каталог"); } } public function execute() { copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName)); } } function executeAction(ActionHandler $actionHandler) { $reflection = new ReflectionObject($actionHandler); foreach ($reflection->getMethods() as $method) { $attributes = $method->getAttributes(SetUp::class); if (count($attributes) > 0) { $methodName = $method->getName(); $actionHandler->$methodName(); } } $actionHandler->execute(); } $copyAction = new CopyFile(); $copyAction->fileName = "/tmp/foo.jpg"; $copyAction->targetDirectory = "/home/user"; executeAction($copyAction); ]]> Синтаксис атрибутов Синтаксис атрибута состоит из следующих компонентов: объявление атрибута начинается с символов #[ и заканчивается символом ], внутри которых через запятую перечисляют названия атрибутов. Название атрибута указывают как относительное, полное или абсолютное, как описывает раздел «Основы пространств имён». Аргументы атрибута необязательны и указываются в круглых скобках (). Аргументы поддерживают только литеральные значения или константные выражения. Поддерживается синтаксис как позиционных, так и именованных аргументов. При запросе экземпляра атрибута через API-интерфейс модуля Reflection название атрибута разрешается в название класса, а аргументы атрибута передаются в конструктор этого класса. Поэтому для каждого атрибута определяют отдельный класс. Синтаксис атрибутов value = $value; } } // b.php namespace Another; use MyExample\MyAttribute; #[MyAttribute] #[\MyExample\MyAttribute] #[MyAttribute(1234)] #[MyAttribute(value: 1234)] #[MyAttribute(MyAttribute::VALUE)] #[MyAttribute(array("key" => "value"))] #[MyAttribute(100 + 200)] class Thing {} #[MyAttribute(1234), MyAttribute(5678)] class AnotherThing {} ]]> Чтение атрибутов через API-интерфейс модуля Reflection Для доступа к атрибутам классов, методов, функций, параметров, свойств и констант класса в API-интерфейсе модуля Reflection предусмотрели метод getAttributes. Метод возвращает массив объектов ReflectionAttribute, каждый из которых умеет возвращать название и аргументы атрибута, и создавать объект класса, которым представили атрибут. Отделение отражённого представления атрибута от экземпляра самого́ атрибутного класса повышает контроль над обработкой ошибок, которые возникают, когда для атрибута не определили класс, допустили опечатку в названии аргумента или пропустили значение аргумента. PHP создаёт объекты атрибутных классов и проверяет корректность аргументов только после вызова метода ReflectionAttribute::newInstance. Пример чтения атрибутов через API-интерфейс модуля Reflection value = $value; } } #[MyAttribute(value: 1234)] class Thing {} function dumpAttributeData($reflection) { $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { var_dump($attribute->getName()); var_dump($attribute->getArguments()); var_dump($attribute->newInstance()); } } dumpAttributeData(new ReflectionClass(Thing::class)); /* string(11) "MyAttribute" array(1) { ["value"]=> int(1234) } object(MyAttribute)#3 (1) { ["value"]=> int(1234) } */ ]]> Вместо перебора каждого атрибута объекта отражения возможно извлечь атрибуты только конкретного атрибутного класса. Для этого при вызове метода в аргументе передают название класса атрибута. Пример чтения конкретных атрибутов через API-интерфейс модуля Reflection getAttributes(MyAttribute::class); foreach ($attributes as $attribute) { var_dump($attribute->getName()); var_dump($attribute->getArguments()); var_dump($attribute->newInstance()); } } dumpMyAttributeData(new ReflectionClass(Thing::class)); ]]> Объявление классов атрибутов Для каждого атрибута рекомендуют определять отдельный класс. В самом простом случае достаточно создать пустой класс и объявить для класса атрибут #[Attribute], который импортируют из глобального пространства имён оператором use. Пример простого класса с атрибутом Типы объявлений для нацеливания атрибута ограничивают путём передачи битовой маски в первом аргументе объявления #[Attribute]. Спецификация ограничения целей, доступных для присваивания атрибутов Вызов метода ReflectionAttribute::newInstance теперь выбросит исключение при объявлении атрибута MyAttribute для другого типа, кроме метода или функции. Атрибутам доступны следующие цели: Attribute::TARGET_CLASS Attribute::TARGET_FUNCTION Attribute::TARGET_METHOD Attribute::TARGET_PROPERTY Attribute::TARGET_CLASS_CONSTANT Attribute::TARGET_PARAMETER Attribute::TARGET_ALL По умолчанию атрибут разрешается назначить классу, свойству или другому объекту рефлексии только один раз. Назначить одинаковые атрибуты одному объекту рефлексии получится, только если объявить атрибут #[Attribute] с флагом Attribute::IS_REPEATABLE в битовой маске. Пример с константой IS_REPEATABLE, которая разрешит назначать атрибут многократно