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

(PHP 8)

Атрибуты помогают добавлять к объявлениям в коде структурированную машиночитаемую метаинформацию. Атрибуты нацеливают на классы, включая анонимные, методы, функции, параметры, свойства и константы класса. Затем во время выполнения кода метаданные, которые определили атрибутами, инспектируют через 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);

?>