站长资讯网
最全最丰富的资讯网站

php8的注解你了解多少?

注解语法

#[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_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_REPEATABLERoute不允许使用两次。

上述提到的,如果没有指定作用范围,会由 PHP 动态地确定范围,如何理解?举例:

<?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],自定义的注解类是不会自动检查作用范围的。除非你使用反射类 ReflectionAttributenewInstance 方法。

举例:

<?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 DoctrineORMMapping {     class Entity {     } }  namespace DoctrineORMAttributes {     class Table {     } }  namespace Foo {     use DoctrineORMMappingEntity;     use DoctrineORMMapping as ORM;     use DoctrineORMAttributes;      #[Entity("imported class")]     #[ORMEntity("imported namespace")]     #[DoctrineORMMappingEntity("absolute from namespace")]     #[Entity("import absolute from global")]     #[AttributesTable()]     function foo() {     } }  namespace {     class Entity {}      dump_attributes((new ReflectionFunction('Foofoo'))->getAttributes()); }  //输出:  array(5) {   [0]=>   array(2) {     ["name"]=>     string(27) "DoctrineORMMappingEntity"     ["args"]=>     array(1) {       [0]=>       string(14) "imported class"     }   }   [1]=>   array(2) {     ["name"]=>     string(27) "DoctrineORMMappingEntity"     ["args"]=>     array(1) {       [0]=>       string(18) "imported namespace"     }   }   [2]=>   array(2) {     ["name"]=>     string(27) "DoctrineORMMappingEntity"     ["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) "DoctrineORMAttributesTable"     ["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 继承了 C1foo 方法,也继承了 foo 的注解。而 C2 覆盖了 C1foo 方法,因此注解也就不存在了。

推荐学习:《PHP8教程》

赞(0)
分享到: 更多 (0)