Атрибуты предлагают возможность добавлять структурированные машиночитаемые метаданные к объявлениям в коде: классы, методы, функции, параметры, свойства и константы класса могут быть целью атрибута. Затем метаданные, определенные атрибутами, можно проверить во время выполнения с помощью API-интерфейсов Reflection . Таким образом, атрибуты можно рассматривать как язык конфигурации, встроенный непосредственно в код.

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

Простым примером использования атрибута является преобразование интерфейса, имеющего необязательные методы, для использования атрибутов. Предположим, ActionHandler интерфейс представляет операцию в приложении, где некоторые реализации обработчика действий требуют настройки, а другие нет. Вместо того, чтобы требовать, чтобы все реализуемые классы реализовывали ActionHandlerметод setUp(), можно использовать атрибут. Одним из преимуществ этого подхода является то, что мы можем использовать атрибут несколько раз.

Пример #1 Реализация необязательных методов интерфейса с атрибутами

<?php
interface ActionHandler
{
    public function execute();
}

#[Attribute]
class SetUp {}

class CopyFile implements ActionHandler
{
    public string $fileName;
    public string $targetDirectory;

    #[SetUp]
    public function fileExists()
    {
        if (!file_exists($this->fileName)) {
            throw new RuntimeException("File does not exist");
        }
    }

    #[SetUp]
    public function targetDirectoryExists()
    {
        if (!file_exists($this->targetDirectory)) {
            mkdir($this->targetDirectory);
        } elseif (!is_dir($this->targetDirectory)) {
            throw new RuntimeException("Target directory $this->targetDirectory is not a directory");
        }
    }

    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);