1
0
mirror of https://github.com/php/doc-zh.git synced 2026-03-23 22:52:08 +01:00
Files
archived-doc-zh/language/attributes.xml
2025-11-02 17:54:34 +08:00

362 lines
9.6 KiB
XML
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision: $ -->
<!-- EN-Revision: 0f14761ba340c6e49797706ac3f0cf1147d97253 Maintainer: daijie Status: ready -->
<!-- CREDITS: mowangjuanzi -->
<chapter xml:id="language.attributes" xmlns="http://docbook.org/ns/docbook">
<title>注解</title>
<sect1 xml:id="language.attributes.overview">
<title>注解概览</title>
<?phpdoc print-version-for="attributes"?>
<para>
PHP 注解为类、方法、函数、参数、属性和常量提供了结构化且机器可读的元数据。它们可以通过<link linkend="book.reflection">反射
API</link> 在运行时进行检查,从而实现动态行为而无需修改代码。注解提供了声明式的方式来为代码添加元数据注释。
</para>
<para>
注解使得功能实现与其使用之间实现解耦。接口通过强制定义方法来规范结构,而注解则为方法、函数、属性和常量在内的多个元素提供元数据。与接口不同,接口强制实现方法,而注解则在不改变代码结构的情况下为其添加注释。
</para>
<para>
注解可以通过提供元数据而非强制结构来补充或替代可选的接口方法。以表示应用程序中操作的 <literal>ActionHandler</literal>
接口为例。某些实现可能需要设置步骤而其他实现则不需要。与其强制所有实现implementing<literal>ActionHandler</literal> 的类都定义
<literal>setUp()</literal> 方法,不如使用注解来表明设置需求。这种方法提高了灵活性,并允许在必要时多次应用属性。
</para>
<example>
<title>用注解实现接口的可选方法</title>
<programlisting role="php">
<![CDATA[
<?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);
]]>
</programlisting>
</example>
</sect1>
<sect1 xml:id="language.attributes.syntax">
<title>注解语法</title>
<para>
注解语法由几个关键组件组成。属性声明以 <literal>#[</literal> 开始,以 <literal>]</literal>
结束。内部可以列出一个或多个注解,注解之间用逗号分隔。注解名称如<link
linkend="language.namespaces.basics">使用命名空间基础</link>中所述,可以是未限定、限定或完全限定的。注解的参数是可选的,并用圆括号
<literal>()</literal> 括起来。参数只能是字面值或常量表达式,同时支持位置参数和命名参数语法。
</para>
<para>
注解名称及其参数会解析为类,并在通过反射 API 请求注解实例时,将参数传递给其构造方法。因此,建议为每个注解都引入对应的类。
</para>
<example>
<title>注解语法</title>
<programlisting role="php">
<![CDATA[
<?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
{
}
]]>
</programlisting>
</example>
</sect1>
<sect1 xml:id="language.attributes.reflection">
<title>使用反射 API 读取注解</title>
<para>
要访问类、方法、函数、参数、属性以及类常量中的注解,可以使用反射 API 提供的 <function>getAttributes</function>
方法。该方法返回包含 <classname>ReflectionAttribute</classname> 实例的数组。这些实例可用于查询注解名称和参数,并可用于实例化所表示注解的对象。
</para>
<para>
将反射注解表示与其实际实例分离,可以更好地控制错误处理,例如缺失的注解类、参数拼写错误或缺少值等问题。注解类的对象只有在调用
<function>ReflectionAttribute::newInstance</function> 之后才会实例化,从而确保参数验证在此时进行。
</para>
<example>
<title>通过反射 API 读取注解</title>
<programlisting role="php">
<![CDATA[
<?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)
}
*/
]]>
</programlisting>
</example>
<para>
无需遍历反射实例上的所有注解,可以通过将注解类名作为参数传递,来仅检索特定注解类的注解。
</para>
<example>
<title>使用反射 API 读取指定的注解</title>
<programlisting role="php">
<![CDATA[
<?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));
]]>
</programlisting>
</example>
</sect1>
<sect1 xml:id="language.attributes.classes">
<title>声明注解类</title>
<para>
建议为每个注解定义单独的类。在最简单的情况下,带有 <literal>#[Attribute]</literal> 声明的空类即可满足需求。可以使用
<literal>use</literal> 语句从全局命名空间导入该注解。
</para>
<example>
<title>简单的 Attribute 类</title>
<programlisting role="php">
<![CDATA[
<?php
namespace Example;
use Attribute;
#[Attribute]
class MyAttribute
{
}
]]>
</programlisting>
</example>
<para>
要限制注解可以应用的声明类型,可以将位掩码作为第一个参数传递给 <literal>#[Attribute]</literal> 声明。
</para>
<example>
<title>目标限定使用的注解</title>
<programlisting role="php">
<![CDATA[
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class MyAttribute
{
}
]]>
</programlisting>
<para>
在另一个类型中声明 <classname>MyAttribute</classname> 会在调用
<function>ReflectionAttribute::newInstance</function> 时抛出异常。
</para>
</example>
<para>可以指定以下目标:</para>
<simplelist>
<member><constant>Attribute::TARGET_CLASS</constant></member>
<member><constant>Attribute::TARGET_FUNCTION</constant></member>
<member><constant>Attribute::TARGET_METHOD</constant></member>
<member><constant>Attribute::TARGET_PROPERTY</constant></member>
<member><constant>Attribute::TARGET_CLASS_CONSTANT</constant></member>
<member><constant>Attribute::TARGET_PARAMETER</constant></member>
<member><constant>Attribute::TARGET_ALL</constant></member>
</simplelist>
<para>
默认情况下,每个声明中一个注解只能使用一次。要允许注解可重复使用,可以在 <literal>#[Attribute]</literal>
声明的位掩码中使用 <constant>Attribute::IS_REPEATABLE</constant> flag 进行指定。
</para>
<example>
<title>使用 IS_REPEATABLE 允许注解在声明中出现多次</title>
<programlisting role="php">
<![CDATA[
<?php
namespace Example;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)]
class MyAttribute
{
}
]]>
</programlisting>
</example>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->