PHP 8 新特性之注解
PHP 8 新特性之注解功能
注解概览
注解功能提供了代码中的声明部分都可以添加结构化、机器可读的元数据的能力, 注解的目标可以是类、方法、函数、参数、属性、类常量。 通过 反射 API 可在运行时获取注解所定义的元数据。 因此注解可以成为直接嵌入代码的配置式语言。
通过注解的使用,在应用中实现功能、使用功能可以相互解耦。 某种程度上讲,它可以和接口(interface)与其实现(implementation)相比较。 但接口与实现是代码相关的,注解则与声明额外信息和配置相关。 接口可以通过类来实现,而注解也可以声明到方法、函数、参数、属性、类常量中。 因此它们比接口更灵活。
注解使用的一个简单例子:将接口(interface)的可选方法改用注解实现。 我们假设接口 ActionHandler 代表了应用的一个操作: 部分 action handler 的实现需要 setup,部分不需要。 我们可以使用注解,而不用要求所有类必须实现 ActionHandler 接口并实现 setUp() 方法。 因此带来一个好处——可以多次使用注解。
示例 #1 用注解实现接口的可选方法
1<?php
2interface ActionHandler
3{
4 public function execute();
5}
6
7#[Attribute]
8class SetUp {}
9
10class CopyFile implements ActionHandler
11{
12 public string $fileName;
13 public string $targetDirectory;
14
15 #[SetUp]
16 public function fileExists()
17 {
18 if (!file_exists($this->fileName)) {
19 throw new RuntimeException("File does not exist");
20 }
21 }
22
23 #[SetUp]
24 public function targetDirectoryExists()
25 {
26 if (!file_exists($this->targetDirectory)) {
27 mkdir($this->targetDirectory);
28 } elseif (!is_dir($this->targetDirectory)) {
29 throw new RuntimeException("Target directory $this->targetDirectory is not a directory");
30 }
31 }
32
33 public function execute()
34 {
35 copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName));
36 }
37}
38
39function executeAction(ActionHandler $actionHandler)
40{
41 $reflection = new ReflectionObject($actionHandler);
42
43 foreach ($reflection->getMethods() as $method) {
44 $attributes = $method->getAttributes(SetUp::class);
45
46 if (count($attributes) > 0) {
47 $methodName = $method->getName();
48
49 $actionHandler->$methodName();
50 }
51 }
52
53 $actionHandler->execute();
54}
55
56$copyAction = new CopyFile();
57$copyAction->fileName = "/tmp/foo.jpg";
58$copyAction->targetDirectory = "/home/user";
59
60executeAction($copyAction);
注解语法
首先,注解声明总是以 #[ 开头,以 ] 结尾来包围。
内部则是一个或以逗号包含的多个注解。
注解的名称按 使用命名空间:基础 章节中描述,可以是非限定、限定、完全限定的名称。
注解的参数是可以选的,以常见的括号()包围。 注解的参数只能是字面值或者常量表达式。
它同时接受位置参数和命名参数两种语法。
示例 #1 注解语法
1<?php
2// a.php
3namespace MyExample;
4
5use Attribute;
6
7#[Attribute]
8class MyAttribute
9{
10 const VALUE = 'value';
11
12 private $value;
13
14 public function __construct($value = null)
15 {
16 $this->value = $value;
17 }
18}
19
20// b.php
21
22namespace Another;
23
24use MyExample\MyAttribute;
25
26#[MyAttribute]
27#[\MyExample\MyAttribute]
28#[MyAttribute(1234)]
29#[MyAttribute(value: 1234)]
30#[MyAttribute(MyAttribute::VALUE)]
31#[MyAttribute(array("key" => "value"))]
32#[MyAttribute(100 + 200)]
33class Thing
34{
35}
36
37#[MyAttribute(1234), MyAttribute(5678)]
38class AnotherThing
39{
40}
使用反射 API 读取注解
反射 API 提供了 getAttributes() 方法, 类、方法、函数、参数、属性、类常量的反射对象可通过它获取相应的注解。
该方法返回了 ReflectionAttribute 实例的数组, 可用于查询注解名称、参数、也可以实例化一个注解。
实例和反射注解的分离使得程序员增加了在丢失反射类、类型错误、丢失参数等情况下的处理能力,也能处理错误。
只有调用 ReflectionAttribute::newInstance() 后,注解类的对象才会以验证过匹配的参数来实例化。
示例 #1 通过反射 API 读取注解
1<?php
2
3#[Attribute]
4class MyAttribute
5{
6 public $value;
7
8 public function __construct($value)
9 {
10 $this->value = $value;
11 }
12}
13
14#[MyAttribute(value: 1234)]
15class Thing
16{
17}
18
19function dumpAttributeData($reflection) {
20 $attributes = $reflection->getAttributes();
21
22 foreach ($attributes as $attribute) {
23 var_dump($attribute->getName());
24 var_dump($attribute->getArguments());
25 var_dump($attribute->newInstance());
26 }
27}
28
29dumpAttributeData(new ReflectionClass(Thing::class));
30/*
31string(11) "MyAttribute"
32array(1) {
33 ["value"]=>
34 int(1234)
35}
36object(MyAttribute)#3 (1) {
37 ["value"]=>
38 int(1234)
39}
40*/
41
42// 通过传入参数:待搜索的注解类名,可返回指定的注解类, 而不需要再到反射类中迭代循环获取所有注解。
示例 #2 使用反射 API 读取指定的注解
1<?php
2
3function dumpMyAttributeData($reflection) {
4 $attributes = $reflection->getAttributes(MyAttribute::class);
5
6 foreach ($attributes as $attribute) {
7 var_dump($attribute->getName());
8 var_dump($attribute->getArguments());
9 var_dump($attribute->newInstance());
10 }
11}
12
13dumpMyAttributeData(new ReflectionClass(Thing::class));
声明注解类
虽然没有严格要求,推荐为每个注解创建一个实际的类。 在这个最简单的例子中,通过 use 语法从全局命名空间引入 #[Attribute] 注解所需要全空的类。
示例 #1 简单的 Attribute 类
1<?php
2
3namespace Example;
4
5use Attribute;
6
7#[Attribute]
8class MyAttribute
9{
10}
要限制指定注解的声明类型,可为 #[Attribute] 注解第一个参数传入字节位掩码设置。
示例 #2 目标限定使用的注解
1<?php
2
3namespace Example;
4
5use Attribute;
6
7#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
8class MyAttribute
9{
10}
在另一个类型中声明 MyAttribute 会在调用 ReflectionAttribute::newInstance() 时抛出异常。 可以指定以下目标:
- Attribute::TARGET_CLASS
- Attribute::TARGET_FUNCTION
- Attribute::TARGET_METHOD
- Attribute::TARGET_PROPERTY
- Attribute::TARGET_CLASS_CONSTANT
- Attribute::TARGET_PARAMETER
- Attribute::TARGET_ALL
注解在每个声明中默认情况下只能使用一次。 如果需要重复,可以在 #[Attribute] 声明中设置字节位掩码。
示例 #3 使用 IS_REPEATABLE 允许注解在声明中出现多次
1<?php
2
3namespace Example;
4
5use Attribute;
6
7#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
8class MyAttribute
9{
10}
参考文章: PHP: 注解 - Manual
发表评论