按 Enter 键跳转到正文

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

发表评论