PHP的注解功能提供了在代码中添加结构化、机器可读的元数据的能力。它可以应用于类、方法、函数、参数、属性和类常量等声明部分。通过反射API,我们可以在运行时获取注解所定义的元数据。因此,注解可以作为一种直接嵌入代码的配置式语言。
一、PHP注解概述
通过注解的使用,在应用中实现功能、使用功能可以相互解耦。 某种程度上讲,它可以和接口(interface)与其实现(implementation)相比较。 但接口与实现是代码相关的,注解则与声明额外信息和配置相关。 接口可以通过类来实现,而注解也可以声明到方法、函数、参数、属性、类常量中。 因此它们比接口更灵活。
注解使用的一个简单例子:将接口(interface)的可选方法改用注解实现。 我们假设接口 ActionHandler 代表了应用的一个操作: 部分 action handler 的实现需要 setup,部分不需要。 我们可以使用注解,而不用要求所有类必须实现 ActionHandler 接口并实现 setUp() 方法。 因此带来一个好处——可以多次使用注解。
用注解实现接口的可选方法:
<?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);
二、PHP注解语法
注解语法包含以下几部分。 首先,注解声明总是以 #[ 开头,以 ] 结尾来包围。 内部则是一个或以逗号包含的多个注解。 注解的名称可以是非限定、限定、完全限定的名称。 注解的参数是可以选的,以常见的括号()包围。 注解的参数只能是字面值或者常量表达式。 它同时接受位置参数和命名参数两种语法。
通过反射 API 请求注解实例时,注解的名称会被解析到一个类,注解的参数则传入该类的构造器中。 因此每个注解都需要引入一个类。
注解语法:
<?php // a.php namespace MyExample; use Attribute; #[Attribute] class MyAttribute { const VALUE = 'value'; private $value; public function __construct($value = null) { $this->value = $value; } } // b.php namespace Another; use MyExample\MyAttribute; #[MyAttribute] #[\MyExample\MyAttribute] #[MyAttribute(1234)] #[MyAttribute(value: 1234)] #[MyAttribute(MyAttribute::VALUE)] #[MyAttribute(array("key" => "value"))] #[MyAttribute(100 + 200)] class Thing { } #[MyAttribute(1234), MyAttribute(5678)] class AnotherThing { }
三、使用反射API读取注解
反射 API 提供了 getAttributes() 方法, 类、方法、函数、参数、属性、类常量的反射对象可通过它获取相应的注解。 该方法返回了 ReflectionAttribute 实例的数组, 可用于查询注解名称、参数、也可以实例化一个注解。
实例和反射注解的分离使得程序员增加了在丢失反射类、类型错误、丢失参数等情况下的处理能力,也能处理错误。 只有调用 ReflectionAttribute::newInstance() 后,注解类的对象才会以验证过匹配的参数来实例化。
通过反射 API 读取注解:
<?php #[Attribute] class MyAttribute { public $value; public function __construct($value) { $this->value = $value; } } #[MyAttribute(value: 1234)] class Thing { } function dumpAttributeData($reflection) { $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { var_dump($attribute->getName()); var_dump($attribute->getArguments()); var_dump($attribute->newInstance()); } } dumpAttributeData(new ReflectionClass(Thing::class)); /* string(11) "MyAttribute" array(1) { ["value"]=> int(1234) } object(MyAttribute)#3 (1) { ["value"]=> int(1234) } */
通过传入参数:待搜索的注解类名,可返回指定的注解类, 而不需要再到反射类中迭代循环获取所有注解。
使用反射 API 读取指定的注解:
<?php function dumpMyAttributeData($reflection) { $attributes = $reflection->getAttributes(MyAttribute::class); foreach ($attributes as $attribute) { var_dump($attribute->getName()); var_dump($attribute->getArguments()); var_dump($attribute->newInstance()); } } dumpMyAttributeData(new ReflectionClass(Thing::class));
四、声明注解类
虽然没有严格要求,推荐为每个注解创建一个实际的类。 在这个最简单的例子中,通过 use 语法从全局命名空间引入 #[Attribute] 注解所需要全空的类。
简单的 Attribute 类:
<?php namespace Example; use Attribute; #[Attribute] class MyAttribute { }
要限制指定注解的声明类型,可为 #[Attribute] 注解第一个参数传入字节位掩码设置。
目标限定使用的注解:
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] class MyAttribute { }
在另一个类型中声明 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] 声明中设置字节位掩码。
使用 IS_REPEATABLE 允许注解在声明中出现多次:
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] class MyAttribute { }