Visão geral dos atributos

(PHP 8)

Os atributos oferecem a capacidade de adicionar informações de metadados estruturadas e legíveis por máquina em declarações no código: classes, métodos, funções, parâmetros, propriedades e constantes de classe podem ser o destino de um atributo. Os metadados definidos pelos atributos podem ser inspecionados em tempo de execução usando as APIs Reflection. Os atributos podem, portanto, ser pensados como uma linguagem de configuração incorporada diretamente no código.

Com atributos, a implementação genérica de um recurso e seu uso concreto em uma aplicação podem ser desacoplados. De certa forma, é comparável a interfaces e suas implementações. Mas onde as interfaces e implementações são sobre código, os atributos são sobre a anotação de informações extras e configuração. As interfaces podem ser implementadas por classes, mas os atributos também podem ser declarados em métodos, funções, parâmetros, propriedades e constantes de classe. Como tal, eles são mais flexíveis do que as interfaces.

Um exemplo simples de uso de atributo é converter uma interface que possui métodos opcionais para usar atributos. Vamos assumir uma interface ActionHandler que representa uma operação em uma aplicação, onde algumas implementações de um manipulador de ações requerem configuração e outras não. Em vez de exigir que todas as classes que implementam ActionHandler implementem um método setUp(), um atributo pode ser usado. Um benefício dessa abordagem é que o atributo pode ser usado várias vezes.

Exemplo #1 Implementando métodos opcionais de uma interface com Atributos

<?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("Arquivo não existe");
}
}

#[
SetUp]
public function
targetDirectoryExists()
{
if (!
file_exists($this->targetDirectory)) {
mkdir($this->targetDirectory);
} elseif (!
is_dir($this->targetDirectory)) {
throw new
RuntimeException("Diretório de destino $this->targetDirectory não é um diretório");
}
}

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);
adicione uma nota

Notas Enviadas por Usuários (em inglês) 2 notes

up
42
Harshdeep
3 years ago
While the example displays us what we can accomplish with attributes, it should be kept in mind that the main idea behind attributes is to attach static metadata to code (methods, properties, etc.). This metadata often includes concepts such as "markers" and "configuration". For example, you can write a serializer using reflection that only serializes marked properties (with optional configuration, such as field name in serialized file). This is reminiscent of serializers written for C# applications.That said, full reflection and attributes go hand in hand. If your use case is satisfied by inheritance or interfaces, prefer that. The most common use case for attributes is when you have no prior information about the provided object/class.<?phpinterface JsonSerializable{    public function toJson() : array;}?>versus, using attributes,<?php#[Attribute]class JsonSerialize {    public function __constructor(public ?string $fieldName = null) {}}class VersionedObject{   #[JsonSerialize]    public const version = '0.0.1';}public class UserLandClass extends VersionedObject{    #[JsonSerialize('call it Jackson')]    public string $myValue;}?>The example above is a little extra convoluted with the existence of the VersionedObject class as I wished to display that with attribute mark ups, you do not need to care how the base class manages its attributes (no call to parent in overriden method).
up
37
Florian Krmer
2 years ago
I've tried Harshdeeps example and it didn't run out of the box and I think it is not complete, so I wrote a complete and working naive example regarding attribute based serialization.<?phpdeclare(strict_types=1);#[Attribute(Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_PROPERTY)]class JsonSerialize{    public function __construct(public ?string $fieldName = null) {}}class VersionedObject{    #[JsonSerialize]    public const version = '0.0.1';}class UserLandClass extends VersionedObject{    protected string $notSerialized = 'nope';    #[JsonSerialize('foobar')]    public string $myValue = '';    #[JsonSerialize('companyName')]    public string $company = '';    #[JsonSerialize('userLandClass')]    protected ?UserLandClass $test;    public function __construct(?UserLandClass $userLandClass = null)    {        $this->test = $userLandClass;    }}class AttributeBasedJsonSerializer {    protected const ATTRIBUTE_NAME = 'JsonSerialize';    public function serialize($object)    {        $data = $this->extract($object);        return json_encode($data, JSON_THROW_ON_ERROR);    }    protected function reflectProperties(array $data, ReflectionClass $reflectionClass, object $object)    {        $reflectionProperties = $reflectionClass->getProperties();        foreach ($reflectionProperties as $reflectionProperty) {            $attributes = $reflectionProperty->getAttributes(static::ATTRIBUTE_NAME);            foreach ($attributes as $attribute) {                $instance = $attribute->newInstance();                $name = $instance->fieldName ?? $reflectionProperty->getName();                $value = $reflectionProperty->getValue($object);                if (is_object($value)) {                    $value = $this->extract($value);                }                $data[$name] = $value;            }        }        return $data;    }    protected function reflectConstants(array $data, ReflectionClass $reflectionClass)    {        $reflectionConstants = $reflectionClass->getReflectionConstants();        foreach ($reflectionConstants as $reflectionConstant) {            $attributes = $reflectionConstant->getAttributes(static::ATTRIBUTE_NAME);            foreach ($attributes as $attribute) {                $instance = $attribute->newInstance();                $name = $instance->fieldName ?? $reflectionConstant->getName();                $value = $reflectionConstant->getValue();                if (is_object($value)) {                    $value = $this->extract($value);                }                $data[$name] = $value;            }        }        return $data;    }    protected function extract(object $object)    {        $data = [];        $reflectionClass = new ReflectionClass($object);        $data = $this->reflectProperties($data, $reflectionClass, $object);        $data = $this->reflectConstants($data, $reflectionClass);        return $data;    }}$userLandClass = new UserLandClass();$userLandClass->company = 'some company name';$userLandClass->myValue = 'my value';$userLandClass2 = new UserLandClass($userLandClass);$userLandClass2->company = 'second';$userLandClass2->myValue = 'my second value';$serializer = new AttributeBasedJsonSerializer();$json = $serializer->serialize($userLandClass2);var_dump(json_decode($json, true));
To Top