广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买
注解语法#[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_CLASSAttribute::TARGET_FUNCTIONAttribute::TARGET_METHODAttribute::TARGET_PROPERTYAttribute::TARGET_CLASS_CONSTANTAttribute::TARGET_PARAMETERAttribute::TARGET_ALLAttribute::IS_REPEATABLE在使用时,#[Attribute]
等同于#[Attribute(Attribute::TARGET_ALL)]
,为了方便,一般使用前者。
1~7都很好理解,分别对应类、函数、类方法、类属性、类常量、参数、所有,前6项可以使用 |
或运算符随意组合,比如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 动态地确定范围,如何理解?举例:
<?phpclass 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 /indexphp test.php /index_aliasphp test.php /test登录后复制
在使用 newInstance
时,定义的作用范围才会生效,检测注解类定义的作用范围和实际修饰的范围是否一致,其它场景并不检测。
<?phpnamespace { 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
语法。<?phpclass IndexController{ #[Route(...["/index", ["get"]])] public function index() { }}登录后复制
虽然在词法解析阶段是通过的,但是在编译阶段会抛出错误。
在使用注解时可以换行//实例化类$route = new Route(path: "/path", methods: ["get"]);0登录后复制注解可以成组使用
//实例化类$route = new Route(path: "/path", methods: ["get"]);1登录后复制注解的继承
注解是可以继承的,也可以覆盖。
//实例化类$route = new Route(path: "/path", methods: ["get"]);2登录后复制
C3
继承了 C1
的 foo
方法,也继承了 foo
的注解。而 C2
覆盖了 C1
的 foo
方法,因此注解也就不存在了。
发表评论