PHP:我是否将事件驱动编程与信号感知接口(信号和插槽/观察者模式)混合在一起?

时间:2012-09-02 01:54:31

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

我见过很多人说Symfony2,Zend Framework 2和其他人都是事件驱动的。

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

由于PHP应用程序是无状态的,因此无法做到这一点。 I.E.让观察者绑定视图观察用户使用界面时的更改。相反,它需要一个新的请求过程才能更新视图。所以,这不是一个事件,而是一个全新的请求

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

在这里你可以阅读:

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引入的信号和插槽模式更相关(观察者模式实现)

作为一个例子,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. 信号和插槽模式
  4. 这些术语只是通用模式吗?因此,实现观察者模式的所有内容都可以被视为事件驱动的吗?

    更新

    Zend Framework 2

      

    关于AOP的文章我上面已经联系过了   (http://mwop.net/blog/251-Aspects,-Filters,-and-Signals,-Oh,-My!.html)   由Matthew Weier O'Phinney(ZF Leader)撰写。 IIRC,它没有   提到“事件驱动”,只是信号和插槽。

    Symfony 2

      

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

    两个框架似乎都使用信号和插槽中的拦截过滤器模式,以便在请求过程中处理同步事件。

2 个答案:

答案 0 :(得分:16)

  

免责声明:这是一个很长的答案,但我认为值得一读   所有参考文献。恕我直言,它会得出一个确定的答案。

在过去的几天里,我一直在努力解决这个问题,如果我已经正确地阅读了,那么答案就是:

  

事件驱动!==请求驱动

     

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

     

这样做的结果是管理国家的责任   转移。在请求协作中,您努力确保每件作品   数据有一个家,如果你愿意,你可以从那个家里查找   它。这个家负责数据的结构,它需要多长时间   存储,如何访问它。在事件协作场景中   我们欢迎新数据来源忘记第二个数据   传递给它的消息端点。"

     

Martin Fowler - Event Collaboration (Queries section)

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

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

  

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

     

另一种合作方式是Event Collaboration。在这种风格   你从来没有一个组件要求别人做任何事情   当任何变化时,每个组件都会发出事件信其他   组件听取该事件并做出反应但是他们希望如此。该   众所周知的观察者模式是事件协作的一个例子。

     

Martin Fowler - Focus on events (section: Using events to collaborate)

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

真实世界的例子

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

  

注意:虽然,它可以适应事件驱动

Zend Framework 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);
}

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

但是,有一点需要注意:这是表示层(Controller + View)。域层确实可以是事件驱动的,但是你看到的几乎所有应用程序都不是这种情况。 **事件驱动和请求驱动之间存在混合:

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

控制器组件不是事件驱动的。它直接与服务组件通信。 相反,服务应该订阅控制器上升的事件,这是表示层的一部分。 (我将在本答案结尾处指出有关事件驱动域模型的参考资料)。

Symfony 2

现在让我们在Symfony2 Application / FrontController上检查一下:\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的情况并非如此&# 39; s作为构造函数参数注入)。

相反,要成为完全事件驱动的组件,它应该与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

......等等

答案 1 :(得分:8)

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

以一种高度概括的方式,您可以将HTTP视为Web用于桌面计算的BIOS。实际上,只需稍微进一步,您就可以轻松地看到Web的隐含事件驱动特性。你说“这不是一个事件,而是一个全新的请求”,我回答说,“一个全新的请求一个事件”,我的意思是在设计模式意义上的这个词。它具有与用户与应用程序交互相关的具体语义。

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

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

编辑/更多想法

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

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

的状态

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