【问题标题】:PHP: Am I mixing up event-driven programming with signals-aware interfaces (Signal and Slots / Observer Pattern)?PHP:我是否将事件驱动编程与信号感知接口(信号和插槽/观察者模式)混为一谈?
【发布时间】:2012-08-27 07:21:05
【问题描述】:

我看到很多人说 Symfony2、Zend Framework 2 等都是事件驱动的。

在桌面世界中,通过事件驱动编程,我了解到应用程序将在其状态发生变化时通知其观察者。

因为 PHP 应用程序是无状态的,所以没有办法做这样的事情。 IE。当用户使用界面时,让观察者绑定到视图观察变化。相反,它需要一个新的请求进程来更新视图。所以,这不是一个事件,而是一个全新的请求

另一方面,有一个类似的概念:事件驱动架构。

在这里你可以同时阅读:

http://en.wikipedia.org/wiki/Event-driven_programming

http://en.wikipedia.org/wiki/Event-driven_architecture

这里是另一个:

http://en.wikipedia.org/wiki/Signal_programming

信号是对进程发生事件的通知。 信号有时被描述为软件中断。信号是 类似于硬件中断,因为它们会中断正常的 程序的执行流程;在大多数情况下,不可能 准确预测信号何时到达。

  • Stackoverflow [singals] 标签说明

而且,我之前所说的事件驱动,似乎更接近于Qt引入的Signals and Slots Pattern(观察者模式实现)

例如,Prado 框架声称是事件驱动的:

http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Applications(应用程序生命周期部分)

http://www.pradosoft.com/docs/manual/System/TApplication.html#methodonEndRequest

IIRC,这不是一个事件驱动的应用程序,而只是实现observable Interface 的类使用的插件挂钩(信号和插槽)。我的意思是,考虑到桌面应用程序使用事件的方式和无状态应用程序使用事件(作为插件)的方式:第一次使用整个应用程序的事件,包括视图,最后一次仅用于服务器端操作。

一个与面向切面的编程(带有信号和槽)更相关,另一个与横切关注点/AOP 无关。换句话说,它与应用程序状态更相关。

那么,这些术语之间实际上是什么关系以及它们之间的区别是什么?

  1. 事件驱动编程
  2. 事件驱动架构
  3. 信号和槽模式

这些术语只是通用模式吗?因此,所有实现观察者模式的东西都可以被认为是事件驱动的?

更新

Zend 框架 2

我在上面链接的关于 AOP 的文章 (http://mwop.net/blog/251-Aspects,-Filters,-and-Signals,-Oh,-My!.html) 由 Matthew Weier O'Phinney(采埃孚领导者)撰写。 IIRC,它没有 提到“事件驱动”,只是信号和插槽。

Symfony 2

Symfony2 EventDispatcher 组件描述没有提及 用于“事件驱动”应用程序: http://symfony.com/doc/current/components/event_dispatcher/introduction.html 它仅包含对“事件”的引用(实际上,由信号和插槽处理)。

两个框架似乎都在 Signal 和 Slots 中使用 Intercepting Filter Pattern 来处理请求过程中的同步事件。

【问题讨论】:

  • 由于 PHP 是无状态的 ... 但是...会话?饼干?数据库?
  • 信号这个词有时专门用来表示同步事件(样本序列、视频帧等,具有恒定的采样率或帧率)而不是异步事件,而事件这个词并且数据流通常用于异步事件队列......我会说这些事件比信号更接近事件。
  • 如果我们移除 PHP 后端并附加一个桌面 GUI,它的工作方式是否与事件驱动编程中的工作方式大致相同?
  • Symfony 事件驱动是关于脚本生命周期中的特定时刻:松散地说,您可以告诉框架侦听特定事件 [e.g.身份验证]并在那一刻执行操作/注入东西[例如根据用户的权限将用户重定向到页面];与响应用户浏览器交互无关。
  • @arxanas 查看更新部分。

标签: php oop design-patterns signals event-driven


【解决方案1】:

免责声明:答案很长,但我认为值得一读 它的所有参考资料。恕我直言,它导致了一个确凿的答案。

过去几天我一直在努力解决这个问题,如果我没看错的话,答案是:

事件驱动!== 请求驱动

"[...] 我发现这是事件中最有趣的区别 协作,用 Jon Udell 的话说:请求驱动的软件会说话 说话时,事件驱动的软件会在有话要说时说话。

这样做的结果是管理状态的责任 转移。在请求合作中,您努力确保每一件 的数据有一个家,如果需要,您可以从该家查找 它。这个家负责数据的结构,多长 存储,如何访问它。在事件协作场景中 欢迎新数据的来源 忘记数据 传递给它的消息端点。”

马丁·福勒 - Event Collaboration (Queries section)

基于该断言 IIRC,现代 PHP 框架实现了观察者模式 + 拦截过滤器 + 信号和插槽,以便在请求周期内触发一些事件。

但是,尽管它采用了一些事件驱动架构的思想,但似乎并不支持整个框架是事件驱动的(即 Symfony2 是一个事件驱动的框架)。

我们习惯于将程序分成多个组件 一起合作。 (我在这里使用了模糊的“组件”一词 故意,因为在这种情况下,我的意思很多:包括 一个程序内的对象和多个跨进程通信的进程 网络。)使他们协作的最常见方式是 请求/响应样式。如果客户对象想要从 salesman 对象,它调用 salesman 对象上的一个方法来询问它 用于该数据。

另一种协作方式是事件协作。在这种风格 您永远不会有一个组件要求另一个组件做任何事情,而是 当任何事情发生变化时,每个组件都会发出一个事件信号。其他 组件侦听该事件并根据他们的意愿做出反应。这 著名的观察者模式就是事件协作的一个例子。

马丁·福勒 - Focus on events (section: Using events to collaborate)

我认为 PHP 应用程序更接近于事件驱动而不是请求驱动只有在关注事件时。如果这些应用程序/框架仅将事件用于横切关注点 (AOP),那么它就不是事件驱动的。同样,您不会仅仅因为您有一些域对象和单元测试而将其称为测试驱动或域驱动。

现实世界的例子

我选择了一些示例来说明为什么这些框架不是完全由事件驱动的。尽管有 AOP 事件,但一切都是请求驱动的

注意:虽然,它可以调整为事件驱动

Zend 框架 2

让我们检查\Zend\Mvc\Application 组件:

它实现了\Zend\EventManager\EventManagerAwareInterface 并依赖于描述可能事件的\Zend\Mvc\MvcEvent

class MvcEvent extends Event
{
    /**#@+
     * Mvc events triggered by eventmanager
     */
    const EVENT_BOOTSTRAP      = 'bootstrap';
    const EVENT_DISPATCH       = 'dispatch';
    const EVENT_DISPATCH_ERROR = 'dispatch.error';
    const EVENT_FINISH         = 'finish';
    const EVENT_RENDER         = 'render';
    const EVENT_ROUTE          = 'route';

    // [...]
}

\Zend\Mvc\Application 组件本身是事件驱动的,因为它不直接与其他组件通信,而是仅触发事件:

/**
 * Run the application
 *
 * @triggers route(MvcEvent)
 *           Routes the request, and sets the RouteMatch object in the event.
 * @triggers dispatch(MvcEvent)
 *           Dispatches a request, using the discovered RouteMatch and
 *           provided request.
 * @triggers dispatch.error(MvcEvent)
 *           On errors (controller not found, action not supported, etc.),
 *           populates the event with information about the error type,
 *           discovered controller, and controller class (if known).
 *           Typically, a handler should return a populated Response object
 *           that can be returned immediately.
 * @return ResponseInterface
 */
public function run()
{
    $events = $this->getEventManager();
    $event  = $this->getMvcEvent();

    // Define callback used to determine whether or not to short-circuit
    $shortCircuit = function ($r) use ($event) {
        if ($r instanceof ResponseInterface) {
            return true;
        }
        if ($event->getError()) {
            return true;
        }
        return false;
    };

    // Trigger route event
    $result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit);
    if ($result->stopped()) {
        $response = $result->last();
        if ($response instanceof ResponseInterface) {
            $event->setTarget($this);
            $events->trigger(MvcEvent::EVENT_FINISH, $event);
            return $response;
        }
        if ($event->getError()) {
            return $this->completeRequest($event);
        }
        return $event->getResponse();
    }
    if ($event->getError()) {
        return $this->completeRequest($event);
    }

    // Trigger dispatch event
    $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);

    // Complete response
    $response = $result->last();
    if ($response instanceof ResponseInterface) {
        $event->setTarget($this);
        $events->trigger(MvcEvent::EVENT_FINISH, $event);
        return $response;
    }

    $response = $this->getResponse();
    $event->setResponse($response);

    return $this->completeRequest($event);
}

那是事件驱动的:你不知道将使用哪个路由器、调度程序和视图渲染器的代码,你只知道这些事件将被触发。您可以挂钩几乎任何兼容的组件来侦听和处理事件。组件之间没有直接的通信。

但是,需要注意一件重要的事情:这是表示层(控制器 + 视图)。领域层确实可以是事件驱动的,但并非您看到的几乎所有应用程序都是如此。 **事件驱动和请求驱动之间存在混合:

// albums controller
public function indexAction()
{
    return new ViewModel(array(
        'albums' => $this->albumsService->getAlbumsFromArtist('Joy Division'),
    ));
}

控制器组件不是事件驱动的。它直接与服务组件通信。 服务应该订阅控制器引发的事件,这是表示层的一部分。 (我会在这个答案的末尾指出关于什么是事件驱动的域模型的参考资料)。

Symfony 2

现在让我们在 Symfony2 应用程序/前端控制器上检查相同的内容:\Symfony\Component\HttpKernel\HttpKernel

确实有请求期间的主要事件:Symfony\Component\HttpKernel\KernelEvents

/**
 * Handles a request to convert it to a response.
 *
 * Exceptions are not caught.
 *
 * @param Request $request A Request instance
 * @param integer $type    The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
 *
 * @return Response A Response instance
 *
 * @throws \LogicException If one of the listener does not behave as expected
 * @throws NotFoundHttpException When controller cannot be found
 */
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
    // request
    $event = new GetResponseEvent($this, $request, $type);
    $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);

    if ($event->hasResponse()) {
        return $this->filterResponse($event->getResponse(), $request, $type);
    }

    // load controller
    if (false === $controller = $this->resolver->getController($request)) {
        throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo()));
    }

    $event = new FilterControllerEvent($this, $controller, $request, $type);
    $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
    $controller = $event->getController();

    // controller arguments
    $arguments = $this->resolver->getArguments($request, $controller);

    // call controller
    $response = call_user_func_array($controller, $arguments);

    // view
    if (!$response instanceof Response) {
        $event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
        $this->dispatcher->dispatch(KernelEvents::VIEW, $event);

        if ($event->hasResponse()) {
            $response = $event->getResponse();
        }

        if (!$response instanceof Response) {
            $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));

            // the user may have forgotten to return something
            if (null === $response) {
                $msg .= ' Did you forget to add a return statement somewhere in your controller?';
            }
            throw new \LogicException($msg);
        }
    }

    return $this->filterResponse($response, $request, $type);
}

但是除了“事件能力”之外,它还直接与 ControllerResolver 组件通信,因此自请求过程开始以来它并不是完全由事件驱动的,尽管它会触发一些事件并允许某些组件是可插拔的(即不是作为构造函数参数注入的 ControllerResolver 的情况)。

相反,要成为一个完全事件驱动的组件,它应该与 ZF2 应用程序组件一样:

    // Trigger dispatch event
    $result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);

普拉多

我没有足够的时间研究源代码,但起初它似乎不是以SOLID 的方式构建的。无论哪种方式,什么是类似 MVC 的框架上的控制器,Prado 将其称为 TPage(尚不确定):

http://www.pradosoft.com/demos/blog-tutorial/?page=Day3.CreateNewUser

而且它确实直接与组件通信:

class NewUser extends TPage
{
    /**
     * Checks whether the username exists in the database.
     * This method responds to the OnServerValidate event of username's custom validator.
     * @param mixed event sender
     * @param mixed event parameter
     */
    public function checkUsername($sender,$param)
    {
        // valid if the username is not found in the database
        $param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null;
    }
    [...]
}

我了解TPage 是一个事件侦听器,可以插入。但它不会使您的域模型成为事件驱动的。所以我认为,在某种程度上,它更接近于 ZF2 提案。

事件驱动示例

为了完成这个冗长的答案,下面是一个成熟的事件驱动应用程序应该具备的:

将应用程序与域事件解耦 http://www.whitewashing.de/2012/08/25/decoupling_applications_with_domain_events.html

事件溯源 http://martinfowler.com/eaaDev/EventSourcing.html

领域事件模式 http://martinfowler.com/eaaDev/DomainEvent.html

活动协作 http://martinfowler.com/eaaDev/EventCollaboration.html

事件拦截 http://martinfowler.com/bliki/EventInterception.html

消息端点 http://www.enterpriseintegrationpatterns.com/MessageEndpoint.html

...等等

【讨论】:

  • 这太棒了!谢谢你。 :)
【解决方案2】:

PHP 不是无状态的,HTTP 是。简单地说,我们基本上在无状态技术之上构建了一个层,我们可以在该层上实现有状态设计。总而言之,PHP 和您选择的数据存储拥有所需的所有工具,可以通过会话标记化来构建基于事件驱动模式的应用程序设计。

以一种高度概括的方式,您可以将 HTTP 视为用于 Web 的,就像 BIOS 用于桌面计算一样。事实上,只要稍微深入一点,您就可以很容易地看到 Web 隐含的事件驱动性质。你说“这不是一个事件,而是一个全新的请求”,而我回过头来说,“一个全新的请求一个事件”,我的意思是在这个词的设计模式意义上。它具有与用户与应用程序交互相关的具体语义含义。

基本上,通过 MVC 和 Front Controller 等模式(以及通过 HTTP cookie 和 PHP 会话的机制),我们只需恢复会话状态,然后响应事件,相应地修改该状态。

我喜欢思考 REST 的本质:具象状态转移......但我要补充一点,我们不应该忘记仅在 UI 事件发生时才转移状态的隐含含义。因此,我们维护我们与 HTTP 签订的合同,我们仅在模型的“表示状态”(即文档、JSON 等)中“说”,但这只是我们的方言。其他系统选择用canvas坐标、信号db等说话。

编辑/更多想法

所以我一直在思考它,我认为有一个概念可以说明在通过 HTTP 讨论 PHP 领域中的这些模式时有点模棱两可:确定性。具体来说,一旦接收到请求,PHP 执行的路径是确定的,这正是为什么在 PHP 中考虑“事件驱动”架构非常困难的原因。我的想法是,我们应该考虑比 PHP 更高的一个级别,即与用户交互的更大“会话”。

在桌面计算中,我们使用运行循环和有状态的上下文来“等待”事件。但是,我会争辩说,网络实际上是对这种架构的改进(在大多数情况下),但最终相同的模式当事件发生时,我们引导我们的状态,而不是运行循环和无限持续时间状态,然后处理该事件。我们不只是将该状态保存在内存中并等待下一个事件,而是存档该状态并关闭资源。在某种意义上它可以被认为效率较低(我们需要在每个“事件”处加载状态),但它也可以被称为更高效,因为内存中永远不会无缘无故 idle state .我们只加载实际被消耗/操作的状态

因此,通过这种方式,将通过 HTTP 的 PHP 视为在 级别进行事件驱动,同时放心,任何给定的执行确实是确定性的,而实际上根本不是事件驱动的。但是,我们实现了前端控制器和 MVC 模式,以便我们可以为应用程序开发人员提供熟悉的偶数驱动挂钩的结构。当您使用一个体面的框架时,您只需说“我想知道用户何时注册,并且该用户当时应该可供我修改”。这是事件驱动的开发。你不应该担心框架已经引导环境(几乎)只是为了调用你的钩子(相对于更传统的概念,即环境已经存在并且你只是通知了一个事件)。这就是以事件驱动的方式开发 PHP 的意义所在。控制器确定(基于请求)哪个事件正在发生,并使用它设计使用的任何机制(即观察者模式、钩子架构等)来允许您的代码处理事件、响应事件,或任何最适合您的特定框架语义的命名法。

【讨论】:

  • 我的意思是:PHP 应用程序是无状态的,我拼错了。但是+1提醒。那么,您是否断言它是所有事件驱动的,但名称不同?通过在请求过程中添加由事件触发的插件钩子并让控制器直接与服务通信,可以认为应用程序是事件驱动的吗?
  • 更新其他想法/澄清
  • 我进行了一些研究并获得了一些见解,请查看:stackoverflow.com/a/12240685/260610
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多