PHP Conference Fukuoka 2025

Descripción general de atributos

(PHP 8)

Los atributos de PHP ofrecen metadatos estructurados y legibles por máquinas para clases, métodos, funciones, parámetros, propiedades y constantes. Pueden ser inspeccionados en tiempo de ejecución a través de la API de Reflection, lo que permite un comportamiento dinámico sin modificar el código. Los atributos proporcionan una forma declarativa de anotar el código con metadatos.

Los atributos permiten desacoplar la implementación de una funcionalidad de su uso. Mientras que las interfaces definen la estructura mediante la imposición de métodos, los atributos proporcionan metadatos en varios elementos, incluidos métodos, funciones, propiedades y constantes. A diferencia de las interfaces, que obligan a implementar métodos, los atributos anotan el código sin alterar su estructura.

Los atributos pueden complementar o reemplazar métodos opcionales de una interfaz al proporcionar metadatos en lugar de una estructura impuesta. Considera una interfaz ActionHandler que representa una operación en una aplicación. Algunas implementaciones pueden necesitar un paso de configuración, mientras que otras no. En lugar de obligar a todas las clases que implementan ActionHandler a definir un método setUp(), un atributo puede indicar los requisitos de configuración. Este enfoque aumenta la flexibilidad, permitiendo que los atributos se apliquen varias veces cuando sea necesario.

Ejemplo #1 Implementación de métodos opcionales de una interfaz mediante 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("El archivo no existe");
}
}

#[
SetUp]
public function
targetDirectoryExists()
{
if (!
file_exists($this->targetDirectory)) {
mkdir($this->targetDirectory);
} elseif (!
is_dir($this->targetDirectory)) {
throw new
RuntimeException("El directorio de destino $this->targetDirectory no es un directorio");
}
}

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);
add a note

User Contributed Notes 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