PHP的注解功能提供了在代碼中添加結(jié)構(gòu)化、機器可讀的元數(shù)據(jù)的能力。它可以應用于類、方法、函數(shù)、參數(shù)、屬性和類常量等聲明部分。通過反射API,我們可以在運行時獲取注解所定義的元數(shù)據(jù)。因此,注解可以作為一種直接嵌入代碼的配置式語言。
一、PHP注解概述
通過注解的使用,在應用中實現(xiàn)功能、使用功能可以相互解耦。 某種程度上講,它可以和接口(interface)與其實現(xiàn)(implementation)相比較。 但接口與實現(xiàn)是代碼相關的,注解則與聲明額外信息和配置相關。 接口可以通過類來實現(xiàn),而注解也可以聲明到方法、函數(shù)、參數(shù)、屬性、類常量中。 因此它們比接口更靈活。
注解使用的一個簡單例子:將接口(interface)的可選方法改用注解實現(xiàn)。 我們假設接口 ActionHandler 代表了應用的一個操作: 部分 action handler 的實現(xiàn)需要 setup,部分不需要。 我們可以使用注解,而不用要求所有類必須實現(xiàn) ActionHandler 接口并實現(xiàn) setUp() 方法。 因此帶來一個好處——可以多次使用注解。
用注解實現(xiàn)接口的可選方法:
<?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注解語法
注解語法包含以下幾部分。 首先,注解聲明總是以 #[ 開頭,以 ] 結(jié)尾來包圍。 內(nèi)部則是一個或以逗號包含的多個注解。 注解的名稱可以是非限定、限定、完全限定的名稱。 注解的參數(shù)是可以選的,以常見的括號()包圍。 注解的參數(shù)只能是字面值或者常量表達式。 它同時接受位置參數(shù)和命名參數(shù)兩種語法。
通過反射 API 請求注解實例時,注解的名稱會被解析到一個類,注解的參數(shù)則傳入該類的構(gòu)造器中。 因此每個注解都需要引入一個類。
注解語法:
<?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() 方法, 類、方法、函數(shù)、參數(shù)、屬性、類常量的反射對象可通過它獲取相應的注解。 該方法返回了 ReflectionAttribute 實例的數(shù)組, 可用于查詢注解名稱、參數(shù)、也可以實例化一個注解。
實例和反射注解的分離使得程序員增加了在丟失反射類、類型錯誤、丟失參數(shù)等情況下的處理能力,也能處理錯誤。 只有調(diào)用 ReflectionAttribute::newInstance() 后,注解類的對象才會以驗證過匹配的參數(shù)來實例化。
通過反射 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) } */
通過傳入?yún)?shù):待搜索的注解類名,可返回指定的注解類, 而不需要再到反射類中迭代循環(huán)獲取所有注解。
使用反射 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));
四、聲明注解類
雖然沒有嚴格要求,推薦為每個注解創(chuàng)建一個實際的類。 在這個最簡單的例子中,通過 use 語法從全局命名空間引入 #[Attribute] 注解所需要全空的類。
簡單的 Attribute 類:
<?php namespace Example; use Attribute; #[Attribute] class MyAttribute { }
要限制指定注解的聲明類型,可為 #[Attribute] 注解第一個參數(shù)傳入字節(jié)位掩碼設置。
目標限定使用的注解:
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)] class MyAttribute { }
在另一個類型中聲明 MyAttribute 會在調(diào)用 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] 聲明中設置字節(jié)位掩碼。
使用 IS_REPEATABLE 允許注解在聲明中出現(xiàn)多次:
<?php namespace Example; use Attribute; #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] class MyAttribute { }