注解概览
(PHP 8)
注解功能提供了代码中的声明部分都可以添加结构化、机器可读的元数据的能力, 注解的目标可以是类、方法、函数、参数、属性、类常量。 通过 反射 API 可在运行时获取注解所定义的元数据。 因此注解可以成为直接嵌入代码的配置式语言。
通过注解的使用,在应用中实现功能、使用功能可以相互解耦。 某种程度上讲,它可以和接口(interface)与其实现(implementation)相比较。 但接口与实现是代码相关的,注解则与声明额外信息和配置相关。 接口可以通过类来实现,而注解也可以声明到方法、函数、参数、属性、类常量中。 因此它们比接口更灵活。
注解使用的一个简单例子:将接口(interface)的可选方法改用注解实现。
我们假设接口 ActionHandler
代表了应用的一个操作:
部分 action handler 的实现需要 setup,部分不需要。
我们可以使用注解,而不用要求所有类必须实现 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 notes
Harshdeep ¶
2 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.
<?php
interface 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).
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.
<?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));
备份地址:http://www.lvesu.com/blog/php/language.attributes.overview.php