加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_阳江站长网 (https://www.0662zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

理解 PHP 8 中的 Attributes (注解)

发布时间:2022-09-21 15:42:17 所属栏目:PHP教程 来源:
导读:  说明

  从 PHP 8 开始,我们将能够开始使用 ‘注解’。

  这些注解(在许多其他语言中也称为注释)的目标是以结构化的方式将元数据添加到类、方法、变量等中。

  注解的概念并不新鲜,
  说明
 
  从 PHP 8 开始,我们将能够开始使用 ‘注解’。
 
  这些注解(在许多其他语言中也称为注释)的目标是以结构化的方式将元数据添加到类、方法、变量等中。
 
  注解的概念并不新鲜,我们使用文档块来模拟它们的行为已经有很多年了。
 
  不过,通过添加注解,我们现在有了语言中的语法来表示这种元数据,而不是必须手动解析文档块。
 
  这些都是本帖将要回答的问题。
 
  分析
 
  首先,下面是原生注解的样子:
 
  use \Support\Attributes\ListensTo;
  class ProductSubscriber
  {
      <<ListensTo(ProductCreated::class)>>
      public function onProductCreated(ProductCreated $event) { /* … */ }
      <<ListensTo(ProductDeleted::class)>>
      public function onProductDeleted(ProductDeleted $event) { /* … */ }
  }
  我将在这篇文章的后面展示其他示例,但我认为事件订阅者的示例是一个很好的例子,可以首先解释注解的用法。
 
  另外,我知道,这种语法可能不是您想要或看到的。您可能首选 @、或 @:、或注释或…。不过,这种语法已经被合并了,所以我们最好学会处理和使用它。关于语法,唯一值得一提的是讨论了所有能够实现注解的语法方案,选择此语法有很好的理由。您可以在 RFC 中阅读有关它的简短摘要,也可以在内部列表中阅读关于 RFC 的完整讨论。
 
  首先,自定义注解是简单的类,用 属性对自身进行注释;这个基本属性在最初的 RFC 中被称为 PhpAttribute ,但后来在另一个 RFC 中进行了更改。
 
  下面是它看起来的样子:
 
  <<Attribute>>
  class ListensTo
  {
      public string $event;
      public function __construct(string $event)
      {
          $this->event = $event;
      }
  }
  就是这样,很简单,对吧?记住注解的目标:它们的目的是将元数据添加到类和方法中,仅此而已。
 
  例如,它们不应该也不能用于参数输入验证。换句话说:您不能访问传递给注解中的方法的参数。以前有一个 RFC 允许这种行为,但这个 RFC 特别让事情变得更简单。
 
  回到事件订阅者示例:我们仍然需要读取元数据,并在某个地方注册我们的订阅者。我有使用 Laravel 的背景,我会使用服务提供商来做这件事,但也可以自由地想出其他解决方案。
 
  class EventServiceProvider extends ServiceProvider
  {
      // 在现实场景中,
      // 我们会自动解析并缓存所有订阅者。
      // 而不是使用手动数组。
      private array $subscribers = [
          ProductSubscriber::class,
      ];
      public function register(): void
      {
          // 事件调度器从容器中解析
          $eventDispatcher = $this->app->make(EventDispatcher::class);
          foreach ($this->subscribers as $subscriber) {
              // 我们将解析所有注册的监听器。
              // 在 `Subscriber` 类中,
              // 并将其添加到调度器。
              foreach (
                  $this->resolveListeners($subscriber)
                  as [$event, $listener]
              ) {
                  $eventDispatcher->listen($event, $listener);
              }       
          }       
      }
  }
  请注意,如果您不熟悉 [$event,$listener]语法,您可以在我关于数组析构的帖子中快速了解它。
 
  现在让我们看一看 ResolutionveListeners,这就是魔术触发的地方。
 
  private function resolveListeners(string $subscriberClass): array
  {
      $reflectionClass = new ReflectionClass($subscriberClass);
      $listeners = [];
      foreach ($reflectionClass->getMethods() as $method) {
          $attributes = $method->getAttributes(ListensTo::class);
          foreach ($attributes as $attribute) {
              $listener = $attribute->newInstance();
              $listeners[] = [
                  // 在注解上配置的事件
                  $listener->event,
                  // 此事件的监听器
                  [$subscriberClass, $method->getName()],
              ];
          }
      }
      return $listeners;
  }
  您可以看到,与解析注释字符串相比,这样更容易读取元数据。不过php注释,有两个错综复杂的问题值得研究。
 
  首先是 $attribute->newInstance() 的调用。这实际上是我们的自定义属性类被实例化的地方。它将接受订阅服务器类的属性定义中列出的参数,并将它们传递给构造函数。
 
  这意味着,从技术上讲,您甚至不需要构造自定义注解。您可以直接调用 $attribute->getArguments()。此外,实例化类意味着您可以灵活地使用构造函数以任何您喜欢的方式进行解析输入。总而言之,我想说总是使用newInstance()实例化属性会很好。
 
  值得一提的是ReflectionMethod::getAttributes()的使用,该函数返回方法的所有属性。
 
  您可以向其传递两个参数,以过滤其输出。
 
  但是,为了理解这种过滤,您首先需要了解关于注解的另一件事。
 
  这对您来说可能是显而易见的,但我还是想快速地提一下:可以向同一个方法、类、属性或常量添加几个属性。
 
  <<Route(Http::POST, '/products/create')>>
  <<Autowire>>
  class ProductsCreateController
  {
      public function __invoke() { /* … */ }
  }
  记住这一点,就很清楚为什么Reflect*::getAttributes()返回一个数组,那么让我们看看如何过滤它的输出。
 
  假设您正在解析控制器路由,您只对Route注解感兴趣。您可以轻松地将该类作为筛选器传递:
 
  $attributes = $reflectionClass->getAttributes(Route::class);
  第二个参数更改过滤的方式。
 
  您可以传入ReflectionAttribute::IS_INSTANCEOF,它将返回实现给定接口的所有注解。
 
  例如,假设您正在解析容器定义,这依赖于几个属性,您可以这样做:
 
  $attributes = $reflectionClass->getAttributes(
      ContainerAttribute::class,
      ReflectionAttribute::IS_INSTANCEOF
  );
  技术理论
 
  现在您已经了解了注解在实践中是如何工作的,是时候进行更多的理论了,确保您彻底理解它们。
 
  首先,我在前面简单地提到了这一点,可以在几个地方添加注解。
 
  在类中,以及匿名类中;
 
  <<ClassAttribute>>
  class MyClass { /* … */ }
  $object = new <<ObjectAttribute>> class () { /* … */ };
  属性和常量;
 
  <<PropertyAttribute>>
  public int $foo;
  <<ConstAttribute>>
  public const BAR = 1;
  方法和功能;
 
  <<MethodAttribute>>
  public function doSomething(): void { /* … */ }
  <<FunctionAttribute>>
  function foo() { /* … */ }
  以及闭包;
 
  $closure = <<ClosureAttribute>> fn() => /* … */;
  方法和功能的参数;
 
  function foo(<<ArgumentAttribute>> $bar) { /* … */ }
 

(编辑:应用网_阳江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!