Attributes overview

Оновлено: 09.05.2023

(PHP 8)

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