2024年PHP日本大会

属性概述

(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);
添加注释

用户贡献的注释 2条注释

42
Harshdeep
2年前
虽然此示例向我们展示了可以使用属性实现什么,但应记住,属性背后的主要思想是将静态元数据附加到代码(方法、属性等)。

此元数据通常包括“标记”和“配置”等概念。例如,您可以编写一个使用反射的序列化器,该序列化器仅序列化标记的属性(具有可选配置,例如序列化文件中的字段名称)。这让人想起为C#应用程序编写的序列化器。

也就是说,完整的反射和属性是相辅相成的。如果您的用例可以通过继承或接口满足,则优先选择它们。属性最常见的用例是当您事先没有关于提供的对象/类的信息时。

<?php
interface JsonSerializable
{
public function
toJson() : array;
}
?>

与使用属性相比:
<?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;
}

?>
以上示例由于`VersionedObject`类的存在而显得有些复杂,我想展示的是:使用属性标记时,无需关心基类如何管理其属性(无需在重写方法中调用父类方法)。
37
Florian Krmer
2年前
我尝试了Harshdeep的示例,但它无法直接运行,我认为它也不完整,所以我编写了一个完整的、可运行的关于基于属性的序列化的简单示例。

<?php
declare(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