PHP Conference Fukuoka 2025

アトリビュートの概要

(PHP 8)

PHP のアトリビュートは、クラス、メソッド、関数、パラメータ、プロパティ、定数に、 構造化され、かつマシンが読み取り可能なメタデータを提供します。 これらは リフレクション 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);
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