jahudka/component-events

将事件转发到延迟创建的组件

v2.0.2 2021-01-29 22:27 UTC

This package is auto-updated.

Last update: 2024-08-29 04:43:20 UTC


README

Latest Stable Version License Tests

此包提供了一种懒加载桥接,用于各种事件派发器和Nette组件模型。它默认与symfony/event-dispatcherdoctrine/event-managercontributte/nextras-orm-events集成,但可以轻松添加其他项目。组件对事件的响应的主要用例当然是重绘片段。

这有什么用呢?如果你正在开发一个以AJAX为主的网站,并且大量使用组件,你可能会遇到一些业务逻辑从组件内部触发,而另一个组件应该注意并可能决定重绘的情况。例如购物车:如果你有一个包含“加入购物车”按钮的产品列表,那么产品列表可能由多个组件组成;“加入购物车”按钮可能是其中之一。如果用户点击按钮,按钮组件将直接处理该事件,因此它可以按需重绘自己,但页面上方右边的购物车组件如何知道它也需要重绘呢?当按钮点击的信号由按钮组件处理时,购物车组件可能甚至还没有创建。这时就出现了ComponentEvents:将购物车组件作为一个事件订阅者,当购物车内容更改时,业务逻辑会发出一个事件。ComponentEvents会在事件派发器中注册事件转发监听器,当事件被发出时,它将转发给组件,如果组件尚未创建,则将在此时懒加载创建。

该包通过在DI容器编译时静态分析所有扩展Nette\Application\UI\Presenter类的服务来工作。由于组件是通过静态定义的createComponent<Name>()方法创建的,因此可以轻松遍历组件树并检查每个组件是否具有相关的接口,前提是工厂方法具有适当的返回类型提示。

安装

您可以使用Composer安装ComponentEvents。

composer require jahudka/component-events

然后您需要在您的配置中注册Jahudka\ComponentEvents\ComponentEventsExtension

配置

ComponentEvents被设计得尽可能自动化 - 每个集成都会自动检测是否应该启用。尽管如此,有时您可能需要覆盖这个魔法。这是如何做到的

componentEvents:
    # string means class name of the integration's Jahudka\ComponentEvents\IBridge implementation
    # (this is how custom integrations are registered, see below); bool means enabled
    # and null means detect automatically (which is the default)
    <bridge name>: string|bool|null

集成

Symfony事件派发器

配置键: symfony

symfony/event-dispatcher及其Nette包装器contributte/event-dispatcher的集成是即插即用的,您不需要做任何特殊的事情就可以使用它们。只需在您希望使用的任何组件中实现Symfony\Component\EventDispatcher\EventSubscriberInterface即可。

使用Symfony事件派发器时唯一的缺点来自Contributte包装器,它会自动订阅实现相应接口的所有服务,而且无法绕过。这意味着它还会自动订阅实现接口的所有展示者 - 这反过来又意味着当访问事件派发器服务时,所有展示者都会自动创建,并且它们会在不是当前展示者的情况下接收事件。Contributte包装器的lazy选项无法解决这个问题,它只是推迟了展示者被创建的时间。除非Contributte的代码有所改变,否则唯一可以让展示者监听事件的方法是将事件派发器服务注入到展示者中,并在startup()方法中让展示者订阅相关事件,然后在shutdown()方法中取消订阅。

或者您可以简单地选择不使用Contributte包装器,直接使用symfony/event-dispatcher包 - 您需要做的只是将EventDispatcher类注册为您的DIC配置中的一个服务。将几个常用内置Nette事件桥接到Symfony EventDispatcher的contributte/event-dispatcher-extra包不依赖于Contributte包装器,因此即使您放弃了包装器,仍然可以使用它。

Doctrine事件管理器

配置键: doctrine

doctrine/event-manager以及任何在DIC中注册了Doctrine\Common\EventManager类或其子类的包装器的集成应该几乎与Symfony集成一样好。这里唯一的区别是,与Symfony中的EventSubscriber接口不同,Doctrine\Common\EventSubscriber接口中的getSubscribedEvents()方法不是static的,这意味着我们在容器重建期间分析实现它的类时无法静态地调用它。组件事件Doctrine桥通过创建一个未调用构造函数的实现类实例,然后调用实例的getSubscribedEvents()方法来解决这个问题 - 但这意味着如果该方法尝试访问应在构造函数中设置的依赖项,调用将失败。在这种情况下,Doctrine桥将简单地忽略该组件。

Nextras ORM事件

配置键: nextras-orm

contributte/nextras-orm-events包是迄今为止最难使用的,因为它与前面的两个不同,它要求您以不同于以往的方式编写代码。具体来说,使用传统的Nextras ORM事件,您必须使用实体类上的注解指定监听器,但使用ComponentEvents,您需要使用监听器类上的注解指定实体。例如:假设您有一个Book实体和一个Author实体。Book实体可以像这样附加一个传统的服务作为监听器

// Book.php

/**
 * @AfterInsert(App\Listener\NewBookListener)
 */
class Book extends Entity {}

// NewBookListener.php

class NewBookListener implements AfterInsertListener {
    public function onAfterInsert(IEntity $entity) : void { /* ... */ }
}

Nextras ORM事件的实现意味着NewBookListener必须在DIC中注册为服务,并且每当创建为Book实体配置的存储库类时,它都会被创建。这与使用ComponentEvents为同一事件配置的组件作为监听器的实现形成对比

// Book.php

class Book extends Entity {}

// AuthorBookCountControl.php

/**
 * @AfterInsert(App\Entity\Book)
 */
class AuthorBookCountControl extends Control implements AfterInsertListener {
    public function onAfterInsert(IEntity $entity) : void {
        $this->redrawControl();
    }
}

代码量几乎相同,只是写在了不同的地方。请注意,您可以在注解中指定多个实体类,方法是将它们作为逗号分隔的列表,命名空间解析与原生的PHP命名空间完全相同,因此会考虑使用use语句。例如,在App\Components命名空间中定义的类上的注解中指定App\Entity\Book时,将解析为App\Components\App\Entity\Book,这是唯一可接受的方式 - 如果您希望它解析为App\Entity\Book,则可以使用use语句并仅指定注解中的Book,或者用反斜杠前缀它 - 就像在PHP代码中那样。

自定义集成

当然,您可以为您想要的任何EventDispatcher创建自定义集成!一个集成由三个类组成

桥接器

集成中的桥接器类必须实现Jahudka\ComponentEvents\IBridge接口。此类在DIC重建期间用于检测集成正在桥接的事件派发器的存在,以及自定义如何创建集成的其他两个类。

分析器

此类负责分析演示者或组件,并提取目标想要订阅的所有事件。它必须实现Jahudka\ComponentEvents\IAnalyser接口。

中继

此类是运行时唯一使用的类。其责任是订阅当前演示者相关的所有相关事件,然后在事件被触发时将事件中继到它们的正确目的地。它必须实现Jahudka\ComponentEvents\IBridge接口。