Maison  >  Article  >  développement back-end  >  Que savez-vous des annotations php8 ?

Que savez-vous des annotations php8 ?

藏色散人
藏色散人avant
2021-09-15 14:43:104752parcourir

syntaxe d'annotation

#[Route]
#[Route()]
#[Route("/path", ["get"])]
#[Route(path: "/path", methods: ["get"])]

En fait, la syntaxe est très similaire à la classe instanciée, sauf que le mot-clé new est manquant. 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(&#39;oldLogAction已废弃,请使用newLogAction代替&#39;)]
    public function oldLogAction(): void 
    {

    }
}

#[Deprecated(&#39;OldLogger已废弃,请使用NewLogger代替&#39;)]
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 = &#39;&#39;,
        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[] = [&#39;name&#39; => $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

Il est à noter que le nom de l'annotation ne peut pas être une variable, mais ne peut être qu'une constante ou une expression constante
rrreee

(path: "/path", méthodes: ["get "]) est la nouvelle syntaxe de php8. Lors de la transmission de paramètres, vous pouvez spécifier des noms de paramètres au lieu de transmettre les paramètres dans l'ordre des paramètres formels. Portée de la classe d'annotation

Lors de la définition d'une classe d'annotation, vous pouvez utiliser la classe d'annotation intégrée #[Attribut] pour définir la portée de la classe d'annotation, ou vous pouvez l'omettre PHP l'ajustera dynamiquement en fonction des scénarios d'utilisation pour définir automatiquement les portées. 🎜🎜Liste de portée d'annotation : 🎜
  • Attribut ::TARGET_CLASS
  • Attribut ::TARGET_FUNCTION
  • Attribut ::TARGET_METHOD
  • Attribut : TARGET_PROPERTY
  • Attribut ::TARGET_CLASS_CONSTANT
  • Attribut ::TARGET_PARAMETER
  • Attribut ::TARGET_ALL
  • Attribut ::IS_REPEATABLE
  • Lorsqu'il est utilisé, #[Attribute] est équivalent à #[Attribute(Attribute::TARGET_ALL)] Pour plus de commodité, le premier est généralement utilisé.
    🎜1~7 sont tous faciles à comprendre. Ils correspondent aux classes, fonctions, méthodes de classe, attributs de classe, constantes de classe, paramètres et tout le reste. Les 6 premiers éléments peuvent être combinés à volonté en utilisant |<.> ou des opérateurs , tels que <code>Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION. (Attribute::TARGET_ALLcontient les 6 premiers éléments, mais n'inclut pas Attribute::IS_REPEATABLE). 🎜🎜Attribute::IS_REPEATABLE Définissez si l'annotation peut être répétée, par exemple : 🎜rrreee🎜Si Attribute::IS_REPEATABLE n'est pas défini, Route n'est pas autorisé à utiliser deux fois. 🎜🎜Comme mentionné ci-dessus, si la portée n'est pas spécifiée, PHP déterminera dynamiquement la portée. Comment comprenez-vous cela ? Exemple : 🎜rrreee🎜La classe d'annotation personnalisée Obsolète mentionnée ci-dessus n'utilise pas la classe d'annotation intégrée #[Attribute] pour définir la portée, donc lorsqu'elle modifie le classe OldLogger, sa portée est définie dynamiquement comme <code>TARGET_CLASS. Lorsqu'il modifie la méthode oldLogAction, sa portée est définie dynamiquement comme TARGET_METHOD. Pour le résumer en une phrase, partout où il sera modifié, sa portée sera là🎜🎜Il est à noter qu'après avoir défini la portée, lors de la phase de compilation, en plus du construit -dans la classe d'annotation #[Attribut], les classes d'annotations personnalisées ne vérifieront pas automatiquement la portée. Sauf si vous utilisez la méthode newInstance de la classe de réflexion ReflectionAttribute. 🎜🎜Exemple : 🎜rrreee🎜L'erreur Erreur fatale : l'attribut "Attribut" ne peut pas cibler la fonction (cibles autorisées : classe) sera signalée ici, car la portée de l'annotation intégrée la classe est TARGET_CLASS , ne peut être utilisée que pour modifier des classes, pas des fonctions Étant donné que la portée de la classe d'annotation intégrée est uniquement TARGET_CLASS, elle ne peut pas être modifiée. à plusieurs reprises. 🎜🎜Les classes d'annotation personnalisées ne vérifieront pas la portée lors de la compilation. 🎜rrreee🎜De cette façon, aucune erreur ne sera signalée. Alors à quoi ça sert de définir le périmètre ? Regardons un exemple complet. 🎜rrreeerrreee🎜La portée définie ne prendra effet que lors de l'utilisation de newInstance. Il est vérifié si la portée définie par la classe d'annotation est cohérente avec la portée réelle modifiée. 🎜🎜L'espace de noms d'annotation 🎜rrreee🎜 est le même que l'espace de noms des classes ordinaires. 🎜🎜Quelques autres problèmes auxquels il faut prêter attention🎜
    • Vous ne pouvez pas utiliser la syntaxe unpack dans la liste des paramètres de la classe d'annotation.
    rrreee🎜Bien que cela passe à l'étape d'analyse lexicale, une erreur sera générée lors de l'étape de compilation. 🎜
    • Les lignes peuvent être renvoyées à la ligne lors de l'utilisation d'annotations
    rrreee
    • Les annotations peuvent être utilisées en groupe
    rrreee
    • Héritage des annotations
    🎜Les annotations peuvent être héritées et remplacées. 🎜rrreee🎜C3 hérite de la méthode foo de C1 et hérite également des annotations de foo. Et C2 couvre la méthode foo de C1, donc l'annotation n'existe pas. 🎜🎜🎜Apprentissage recommandé : "🎜Tutoriel PHP8🎜"🎜🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer