Полный цикл в digital

Атрибуты PHP

Атрибуты были введены в PHP начиная с версии 8.0. Атрибуты — это мощная функция PHP, позволяющая добавлять метаданные к классам, методам или свойствам. Метаданные можно получить программно, что открывает новые возможности для создания более чистого, организованного и эффективного кода.

Атрибуты не оказывают никакого влияния во время выполнения кода. Они будут доступны в Reflection API. Атрибуты делятся на встроенные и пользовательские.

Встроенные атрибуты

PHP обладает рядом встроенных атрибутов, выполняющих различные функции:

Deprecated

#[Deprecated] встроенный атрибут в PHP, который помечает функциональность как устаревшую. Это указывает, что элемент не рекомендуется к использованию и может быть удалён в будущих версиях.

Атрибут принимает два необязательных параметра:

  1. message сообщение, которое объясняет причину устаревания и возможную замену функциональности. Текст сообщения включается в предупреждение об устаревании
  2. since строка, которая указывает, с какого момента устарела функциональность. PHP не проверяет содержание строки, и поэтому иногда строка включает сведения о версии, дате или другие значения, которые считает уместными

Атрибут можно добавить к следующим сущностям:

  1. Класс
  2. Методы класса
  3. Константы класса
  4. Элементы Enum списка
  5. Функции
  6. Константы
#[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 могут использовать этот атрибут для улучшения дополнения кода и обнаружения ошибок

Атрибут можно добавить к следующим сущностям:

  1. Атрибут можно применять только к методам, которые действительно переопределяют метод родительского класса. Если атрибут используется для метода, который не переопределяет метод родителя, PHP выдаст фатальную ошибку при вызове метода
  2. Атрибут нельзя нацеливать на метод __construct(), поскольку PHP не проверяет совместимость сигнатур конструкторов при наследовании
  3. Применим к интерфейсам, гарантирует что родительский интерфейс содержит конкретный метод и что он не был удалён без учёта потомков
  4. Не работает для перечислений — у перечислений могут быть методы, но они не могут иметь родителя, 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, чтобы в конечном итоге предпринять какие-либо действия.

Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!