• 技术文章 >后端开发 >PHP8

    php8的注解你了解多少?

    藏色散人藏色散人2021-09-15 14:43:48转载102

    注解语法

    #[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 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 继承了 C1foo 方法,也继承了 foo 的注解。而 C2 覆盖了 C1foo 方法,因此注解也就不存在了。

    推荐学习:《PHP8教程

    以上就是php8的注解你了解多少?的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:segmentfault,如有侵犯,请联系admin@php.cn删除
    专题推荐:php8 php
    上一篇:来聊聊PHP8中的str_starts_with()函数 下一篇:没有了
    线上培训班

    相关文章推荐

    • 解析PHP8底层内核源码-数组(四)• php8条件判断有哪些?php8中三元运算符是什么意思?• laravel docker sail php8.0如何安装php-imagick• 来聊聊PHP8中的str_starts_with()函数

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网