註解語法
#[Route] #[Route()] #[Route("/path", ["get"])] #[Route(path: "/path", methods: ["get"])]
其實語法跟實例化類別非常相似,只是少了個 new
關鍵字而已。
要注意的是, 註解名不能是變量,只能是常數或常數表達式
//实例化类 $route = new Route(path: "/path", methods: ["get"]);
(path: "/path", methods : ["get"])
是php8
的新語法,傳參的時候可以指定參數名,不依照形參的順序傳參。
註解類別作用範圍
在定義註解類別時,你可以使用內建註解類別#[Attribute]
定義註解類別的作用範圍,也可以省略,由PHP 動態地根據使用場景自動定義範圍。
註解作用範圍清單:
- Attribute::TARGET_CLASS
- Attribute::TARGET_FUNCTION
- Attribute::TARGET_METHOD
- # Attribute::TARGET_PROPERTY
- Attribute::TARGET_CLASS_CONSTANT
- Attribute::TARGET_PARAMETER
- Attribute::TARGET_ALL
- Attribute::IS_REPEATABLELE
#[Attribute]1~7都很好理解,分別對應類別、函數、類別方法、類別屬性、類別常數、參數、所有,前6項可以使用等同於
#[Attribute(Attribute::TARGET_ALL)],為了方便,一般使用前者。
| 或運算子隨意組合,例如
Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION。 (
Attribute::TARGET_ALL包含前6項,但不包含
Attribute::IS_REPEATABLE#)。
Attribute::IS_REPEATABLE 設定該註解是否可以重複,例如:
class IndexController { #[Route('/index')] #[Route('/index_alias')] public function index() { echo "hello!world" . PHP_EOL; } }如果沒有設定
Attribute::IS_REPEATABLE,
Route 不允許使用兩次。
<?php class Deprecated { } class NewLogger { public function newLogAction(): void { //do something } #[Deprecated('oldLogAction已废弃,请使用newLogAction代替')] public function oldLogAction(): void { } } #[Deprecated('OldLogger已废弃,请使用NewLogger代替')] class OldLogger { }上述的自訂註解類別
Deprecated 並沒有使用內建註解類別
#[Attribute] 定義作用範圍,因此當它修飾類別
OldLogger 時,它的作用範圍被動態地定義為
TARGET_CLASS。當它修飾方法
oldLogAction 時,它的作用範圍被動態地定義為
TARGET_METHOD。
一句話概括,就是修飾哪,它的作用範圍就在哪
需要注意的是, 在設定了作用範圍之後,在編譯階段,除了內建註解類#[Attribute],自訂的註解類別是不會自動檢查作用範圍的。除非你使用反射類別
ReflectionAttribute 的
newInstance 方法。
<?php #[Attribute] function foo() { }這裡會報錯誤
Fatal error: Attribute "Attribute" cannot target function (allowed targets: class),因為內建註解類別的作用範圍是
TARGET_CLASS,只能用來修飾類別而不能是函數,
因為內建註解類別的作用範圍只是TARGET_CLASS,所以也不能重複修飾
。
<?php #[Attribute(Attribute::TARGET_CLASS)] class A1 { } #[A1] function foo() {}這樣是不會報錯的。那定義作用範圍有什麼意義呢?看一個綜合實例。
<?php #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::IS_REPEATABLE)] class Route { protected $handler; public function __construct( public string $path = '', public array $methods = [] ) {} public function setHandler($handler): self { $this->handler = $handler; return $this; } public function run() { call_user_func([new $this->handler->class, $this->handler->name]); } } class IndexController { #[Route(path: "/index_alias", methods: ["get"])] #[Route(path: "/index", methods: ["get"])] public function index(): void { echo "hello!world" . PHP_EOL; } #[Route("/test")] public function test(): void { echo "test" . PHP_EOL; } } class CLIRouter { protected static array $routes = []; public static function setRoutes(array $routes): void { self::$routes = $routes; } public static function match($path) { foreach (self::$routes as $route) { if ($route->path == $path) { return $route; } } die('404' . PHP_EOL); } } $controller = new ReflectionClass(IndexController::class); $methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC); $routes = []; foreach ($methods as $method) { $attributes = $method->getAttributes(Route::class); foreach ($attributes as $attribute) { $routes[] = $attribute->newInstance()->setHandler($method); } } CLIRouter::setRoutes($routes); CLIRouter::match($argv[1])->run();
php test.php /index php test.php /index_alias php test.php /test在使用
newInstance 時,定義的作用範圍才會生效,檢測註解類別定義的作用範圍和實際修飾的範圍是否一致,其它場景並不檢測。
<?php
namespace {
function dump_attributes($attributes) {
$arr = [];
foreach ($attributes as $attribute) {
$arr[] = ['name' => $attribute->getName(), 'args' => $attribute->getArguments()];
}
var_dump($arr);
}
}
namespace Doctrine\ORM\Mapping {
class Entity {
}
}
namespace Doctrine\ORM\Attributes {
class Table {
}
}
namespace Foo {
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Attributes;
#[Entity("imported class")]
#[ORM\Entity("imported namespace")]
#[\Doctrine\ORM\Mapping\Entity("absolute from namespace")]
#[\Entity("import absolute from global")]
#[Attributes\Table()]
function foo() {
}
}
namespace {
class Entity {}
dump_attributes((new ReflectionFunction('Foo\foo'))->getAttributes());
}
//输出:
array(5) {
[0]=>
array(2) {
["name"]=>
string(27) "Doctrine\ORM\Mapping\Entity"
["args"]=>
array(1) {
[0]=>
string(14) "imported class"
}
}
[1]=>
array(2) {
["name"]=>
string(27) "Doctrine\ORM\Mapping\Entity"
["args"]=>
array(1) {
[0]=>
string(18) "imported namespace"
}
}
[2]=>
array(2) {
["name"]=>
string(27) "Doctrine\ORM\Mapping\Entity"
["args"]=>
array(1) {
[0]=>
string(23) "absolute from namespace"
}
}
[3]=>
array(2) {
["name"]=>
string(6) "Entity"
["args"]=>
array(1) {
[0]=>
string(27) "import absolute from global"
}
}
[4]=>
array(2) {
["name"]=>
string(29) "Doctrine\ORM\Attributes\Table"
["args"]=>
array(0) {
}
}
}
跟普通類別的命名空間一致。 其它要注意的一些問題
- 不能在註解類別參數清單中使用 unpack
語法。
<?php class IndexController { #[Route(...["/index", ["get"]])] public function index() { } }雖然在詞法解析階段是通過的,但是在編譯階段會拋出錯誤。
- 在使用註解時可以換行
<?php class IndexController { #[Route( "/index", ["get"] )] public function index() { } }
- #註解可以成組使用
<?php class IndexController { #[Route( "/index", ["get"] ), Other, Another] public function index() { } }
- 註解的繼承
<?php class C1 { #[A1] public function foo() { } } class C2 extends C1 { public function foo() { } } class C3 extends C1 { #[A1] public function bar() { } } $ref = new \ReflectionClass(C1::class); print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes())); $ref = new \ReflectionClass(C2::class); print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes())); $ref = new \ReflectionClass(C3::class); print_r(array_map(fn ($a) => $a->getName(), $ref->getMethod('foo')->getAttributes()));
C3 繼承了
C1 的
foo 方法,也繼承了
foo 的註解。而
C2 覆寫了
C1 的
foo 方法,因此註解也就不存在了。