PHP注解

2024-01-04 116

PHP的注解功能提供了在代码中添加结构化、机器可读的元数据的能力。它可以应用于类、方法、函数、参数、属性和类常量等声明部分。通过反射API,我们可以在运行时获取注解所定义的元数据。因此,注解可以作为一种直接嵌入代码的配置式语言。

一、PHP注解概述

通过注解的使用,在应用中实现功能、使用功能可以相互解耦。 某种程度上讲,它可以和接口(interface)与其实现(implementation)相比较。 但接口与实现是代码相关的,注解则与声明额外信息和配置相关。 接口可以通过类来实现,而注解也可以声明到方法、函数、参数、属性、类常量中。 因此它们比接口更灵活。

注解使用的一个简单例子:将接口(interface)的可选方法改用注解实现。 我们假设接口 ActionHandler 代表了应用的一个操作: 部分 action handler 的实现需要 setup,部分不需要。 我们可以使用注解,而不用要求所有类必须实现 ActionHandler 接口并实现 setUp() 方法。 因此带来一个好处——可以多次使用注解。

用注解实现接口的可选方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?<a href="https://www.zzbaike.com/tag/php" title="查看所有文章关于 php" target="_blank">php</a>
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);
<?<a href="https://www.zzbaike.com/tag/php" title="查看所有文章关于 php" target="_blank">php</a> 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
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 请求注解实例时,注解的名称会被解析到一个类,注解的参数则传入该类的构造器中。 因此每个注解都需要引入一个类。

注解语法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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
{
}
<?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 { }
<?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 读取注解:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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)
}
*/
<?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) } */
<?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 读取指定的注解:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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));
<?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));
<?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 类:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace Example;
use Attribute;
#[Attribute]
class MyAttribute
{
}
<?php namespace Example; use Attribute; #[Attribute] class MyAttribute { }
<?php
namespace Example;
use Attribute;
#[Attribute]
class MyAttribute
{
}

要限制指定注解的声明类型,可为 #[Attribute] 注解第一个参数传入字节位掩码设置。

目标限定使用的注解:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class MyAttribute
{
}
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] class MyAttribute { }
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class MyAttribute
{
}

在另一个类型中声明 MyAttribute 会在调用 ReflectionAttribute::newInstance() 时抛出异常。

可以指定以下目标:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL
Attribute::TARGET_CLASS Attribute::TARGET_FUNCTION Attribute::TARGET_METHOD Attribute::TARGET_PROPERTY Attribute::TARGET_CLASS_CONSTANT Attribute::TARGET_PARAMETER Attribute::TARGET_ALL
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 允许注解在声明中出现多次:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class MyAttribute
{
}
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] class MyAttribute { }
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class MyAttribute
{
}
  • 广告合作

  • QQ群号:4114653

温馨提示:
1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。 2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。
PHP注解
上一篇: PHP生成器
PHP注解
下一篇: PHP引用