Введение в атрибуты

(PHP 8)

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

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

Давайте разберём использование атрибутов на простом примере реализации опциональных методов для интерфейса. Допустим, что интерфейс 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);