Атрибуты PHP
Атрибуты были введены в PHP начиная с версии 8.0. Атрибуты — это мощная функция PHP, позволяющая добавлять метаданные к классам, методам или свойствам. Метаданные можно получить программно, что открывает новые возможности для создания более чистого, организованного и эффективного кода.
Атрибуты не оказывают никакого влияния во время выполнения кода. Они будут доступны в Reflection API. Атрибуты делятся на встроенные и пользовательские.
Встроенные атрибуты
PHP обладает рядом встроенных атрибутов, выполняющих различные функции:
Deprecated
#[Deprecated] встроенный атрибут в PHP, который помечает функциональность как устаревшую. Это указывает, что элемент не рекомендуется к использованию и может быть удалён в будущих версиях.
Атрибут принимает два необязательных параметра:
messageсообщение, которое объясняет причину устаревания и возможную замену функциональности. Текст сообщения включается в предупреждение об устареванииsinceстрока, которая указывает, с какого момента устарела функциональность. PHP не проверяет содержание строки, и поэтому иногда строка включает сведения о версии, дате или другие значения, которые считает уместными
Атрибут можно добавить к следующим сущностям:
- Класс
- Методы класса
- Константы класса
- Элементы Enum списка
- Функции
- Константы
#[Deprecated]
function oldFunction($param)
{
return $param;
}
#[Deprecated(message: 'use bar instead', since: 'v42.16.15')]
function oldFunction($param)
{
return $param;
}
#[Deprecated('use bar instead', 'v42.16.15')]
function oldFunction($param)
{
return $param;
}
Override
Аннотация на уровне кода, которая явно объявляет о намерении метода переопределить метод родительского класса:
class Child extends Parent {
#[Override]
public function defaultMethod()
{
// реализация метода
}
}
Цель атрибута:
- Предотвращение ошибок при наследовании, разработчик думает, что переопределяет метод родительского класса, но из-за опечатки или изменения сигнатуры создаёт новый метод. PHP проверяет эту ситуацию на этапе компиляции
- Удобство сопровождения кода, атрибут делает код самодокументируемым, другие разработчики могут быстро определить, какие методы переопределяют функциональность родительского класса
- Улучшенная поддержка IDE, современные IDE могут использовать этот атрибут для улучшения дополнения кода и обнаружения ошибок
Атрибут можно добавить к следующим сущностям:
- Атрибут можно применять только к методам, которые действительно переопределяют метод родительского класса. Если атрибут используется для метода, который не переопределяет метод родителя, PHP выдаст фатальную ошибку при вызове метода
- Атрибут нельзя нацеливать на метод
__construct(), поскольку PHP не проверяет совместимость сигнатур конструкторов при наследовании - Применим к интерфейсам, гарантирует что родительский интерфейс содержит конкретный метод и что он не был удалён без учёта потомков
- Не работает для перечислений — у перечислений могут быть методы, но они не могут иметь родителя, PHP не принимает такое использование и сообщает об ошибке
SuppressWarnings
Подавляет определенные предупреждения для конкретного фрагмента кода:
#[SuppressWarnings("SomeWarning")]
function someFunction()
{
// реализация метода
}
Конфигурация атрибутов
По умолчанию атрибуты могут быть добавлены в нескольких местах, как уже было сказано выше. Однако можно настроить их так, чтобы они могли использоваться только в определенных местах. Включение этого поведения осуществляется путем передачи определенного флага в классе атрибута. В данном примере можно использовать CustomAttribute только на классах, и больше нигде:
#[Attribute(Attribute::TARGET_CLASS)]
class CustomAttribute
{
public string $message;
public function __construct(string $message)
{
$this->message = $message;
}
}
Флаги можно комбинировать, используя |:
#[Attribute(Attribute::TARGET_CLASS|Attribute::TARGET_METHOD)]
class CustomAttribute
{
public string $message;
public function __construct(string $message)
{
$this->message = $message;
}
}
Доступны следующие флаги:
// атрибут может применяться только к классам
Attribute::TARGET_CLASS
// атрибут может применяться только к функциям
Attribute::TARGET_FUNCTION
// атрибут может применяться только к методам класса
Attribute::TARGET_METHOD
// атрибут может применяться только к свойствам класса
Attribute::TARGET_PROPERTY
// атрибут может применяться только к константам класса и перечислениям
Attribute::TARGET_CLASS_CONSTANT
// атрибут может применяться только к параметрам функций или методов
Attribute::TARGET_PARAMETER
// атрибут может быть применен где угодно
Attribute::TARGET_ALL
Создание пользовательских классов атрибутов
Создадим пользовательский класс атрибутов. Это полезно, когда вы хотите инкапсулировать определенное поведение внутри атрибута.
Валидатор свойства
Предположим, вы разрабатываете веб-приложение и хотите создать пользовательский атрибут для валидации длины строки, мы ограничили возможность применения атрибута только свойствами класса:
#[Attribute(Attribute::TARGET_PROPERTY)]
class MaxLengthAttribute
{
public string $maxLength;
public function __construct ($maxLength) {
$this->maxLength = $maxLength;
}
public function checkUsername($username) : string {
if(strlen($username) < $this->maxLength) {
return "Длина строки не привышает $this->maxLength символов";
} else {
return "Длина строки превышает $this->maxLength символов";
}
}
}
class User
{
#[MaxLengthAttribute(20)]
public string $username;
public function __construct ($username) {
$this->username = $username;
}
public function friendly() : string
{
// создаем экземпляр ReflectionClass для class User
$reflectionClass = new ReflectionClass(self::class);
// получаем атрибут
$attributes = $reflectionClass->getProperty('username')->getAttributes();
// если атрибут не найден, генерируем исключение
if ($attributes === []) {
throw new \RuntimeException(
message: 'Атрибут не найден для ' . $this->username,
);
}
// создаем новый экземпляр MaxLengthAttribute и возвращаем значение свойства username
return $attributes[0]->newInstance()->checkUsername($this->username);
}
}
$user = new User('Андрей');
echo $user->friendly();
Подмена строки
Подмена названия для пользователя:
#[Attribute]
final class StatusAttribute
{
// принимаем атрибут
public function __construct (
public string $line,
)
{
// доробатываем атрибут согласно логике
$this->line = $line . "!";
}
}
enum PostStatus : string
{
#[StatusAttribute('Draft')]
case Draft = 'draft';
#[StatusAttribute('Published')]
case Published = 'published';
#[StatusAttribute('In Review')]
case InReview = 'in-review';
#[StatusAttribute('Scheduled to Publish')]
case Scheduled = 'scheduled';
public function friendly(): string
{
// создаем экземпляр ReflectionClassConstant для enum PostStatus
$reflectionClass = new ReflectionClassConstant(
class: self::class,
constant: $this->name,
);
// получаем атрибут
$attributes = $reflectionClass->getAttributes(
name: StatusAttribute::class,
);
// если атрибут не найден, генерируем исключение
if ($attributes === []) {
throw new \RuntimeException(
message: 'Атрибут не найден для ' . $this->name,
);
}
// создаем новый экземпляр Friendly и возвращаем значение свойства line
return $attributes[0]->newInstance()->line;
}
}
// вызываем перечисление
echo PostStatus::InReview->friendly();
Добавление атрибута к свойству не оказывает никакого влияния на выполнение программы. Однако теперь мы можем получить эту информацию с помощью Reflection API, чтобы в конечном итоге предпринять какие-либо действия.