PHP 注解
(PHP 8)
PHP8 开始可以使用注解功能。
注解功能使得代码中的声明部分可以添加结构化、机器可读的元数据,注解作用于类、方法、函数、参数、属性、类常量。
通过反射 API 可在运行时获取注解所定义的元数据,因此注解可以成为直接嵌入代码的配置式语言。
注解概览
通过注解的使用,在应用中实现功能、使用功能可以相互解耦。 某种程度上讲,它可以和接口(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() { mkdir($this->targetDirectory); } 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);
注解语法
注解语法包含以下几部分。 首先,注解声明总是以 #[ 开头,以 ] 结尾来包围。 内部则是一个或以逗号包含的多个注解。 注解的名称按 使用命名空间:基础 章节中描述,可以是非限定、限定、完全限定的名称。 注解的参数是可以选的,以常见的括号()包围。 注解的参数可以是字面值或者常量表达式。 它同时接受位置参数和命名参数两种语法。
通过反射 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 实例的数组, 可用于查询注解名称、参数、也可以实例化一个注解。
实例和反射注解的分离使得程序员增加了在丢失反射类、类型错误、丢失参数等情况下的处理能力,也能处理错误。 只有调用 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()); } } dumpAttributeData(new ReflectionClass(Thing::class));
声明注解类
虽然没有严格要求,推荐为每个注解创建一个实际的类。 在这个最简单的例子中,通过 use 语法从全局命名空间引入 #[Attribute] 注解所需要全空的类。
示例 目标限定使用的注解
<?php namespace Example; use Attribute; #[Attribute] class MyAttribute { }
要限制指定注解的声明类型,可为 #[Attribute] 注解第一个参数传入字节位掩码设置。
示例 简单的 Attribute 类
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] class MyAttribute { }
在另一个类型中声明 MyAttribute 会在调用 ReflectionAttribute::newInstance() 时抛出异常。
注解在每个声明中默认情况下只能使用一次。 如果需要重复,可以在 #[Attribute] 声明中设置字节位掩码。
示例 使用 IS_REPEATABLE 允许注解在声明中出现多次
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] class MyAttribute { }