Key*_*ana 19 php oop design-patterns signals event-driven
我见过很多人说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引入的信号和插槽模式更相关(观察者模式实现)
举个例子,Prado框架声称是事件驱动的:
http://www.pradosoft.com/demos/quickstart/?page=Fundamentals.Applications(应用程序生命周期部分)
http://www.pradosoft.com/docs/manual/System/TApplication.html#methodonEndRequest
IIRC,这不是一个事件驱动的应用程序,而是只是实现该类的类所使用的插件钩子(信号和插槽)observable Interface.我的意思是,考虑桌面应用程序使用事件的方式以及无状态应用程序使用事件的方式(作为插件):第一个使用整个应用程序的事件,包括视图,最后一个仅用于服务器端操作.
一个与面向方面的编程(带信号和插槽)更相关,另一个与横切关注点/AOP 没有特别的关系.换句话说,它与应用程序状态更相关.
那么,实际上这些术语之间的关系以及它们之间的区别是什么?
这些术语只是通用模式吗?因此,实现观察者模式的所有内容都可以被视为事件驱动的?
UPDATE
Zend框架2
我上面链接的关于AOP的文章(http://mwop.net/blog/251-Aspects,-Filters,-and-Signals,-Oh,-My!.html)由Matthew Weier O'Phinney撰写( ZF领袖).IIRC,它没有提到"事件驱动",只提到信号和插槽.
Symfony 2
Symfony2
EventDispatcher组件描述没有提及"事件驱动"应用程序:http: //symfony.com/doc/current/components/event_dispatcher/introduction.html 它只包含对"事件"的引用(确实,由信号和插槽处理).
两个框架似乎都使用信号和插槽中的拦截过滤器模式,以便在请求过程中处理同步事件.
Key*_*ana 16
免责声明:这是一个很长的答案,但我认为值得阅读所有参考资料.恕我直言,它导致一个确定的答案.
在过去的几天里,我一直在努力研究这个问题,如果我已经正确地阅读了,那么答案是:
事件驱动!==请求驱动
"[...]我发现这是事件合作中最有趣的区别,用Jon Udell的话来说:请求驱动的软件说话时说话,事件驱动的软件说话时有话要说.
这样做的结果是管理国家的责任转移.在请求协作中,您努力确保每个数据都有一个家,如果您需要,可以从该家中查找.这个家负责数据的结构,存储的时间,访问方式.在事件协作场景中,欢迎新数据源忘记传递给其消息端点的第二个数据."
Martin Fowler - 事件协作(查询部分)
基于该断言,IIRC,现代PHP框架实现了观察者模式+拦截过滤器+信号和插槽,以便在请求周期中触发某些事件.
但是,尽管它采用了事件驱动架构的一些想法,但它似乎并不支持整个框架是事件驱动的(即Symfony2是一个事件驱动的框架).
我们习惯于将程序划分为多个组合在一起的组件.(我在这里故意使用模糊的'组件'这个词,因为在这个上下文中我指的是很多东西:包括程序中的对象和通过网络进行通信的多个进程.)使它们协作的最常见方式是请求/响应方式.如果客户对象想要销售人员对象中的某些数据,则会调用salesman对象上的方法来询问该数据.
另一种合作方式是Event Collaboration.在这种风格中, 你永远不会有一个组件要求另一个组件做任何事情,而是每个组件在任何变化时发出事件信 其他组件会听取该事件并做出他们希望的反应.众所周知的观察者模式是事件协作的一个例子.
Martin Fowler - 关注事件(部分:使用事件进行协作)
我认为只有当重点放在事件上时,PHP应用程序才更容易被事件驱动而不是请求驱动.如果这些应用程序/框架仅使用事件来处理跨领域问题(AOP),那么它不是事件驱动的.以同样的方式,您不会因为您有一些域对象和单元测试而将其称为测试驱动或域驱动.
我已经选择了一些示例来说明为什么这些框架不是完全由事件驱动的.尽管有AOP事件,但一切都是请求驱动的:
注意:虽然,它可以适应事件驱动
让我们来看看\ 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';
// [...]
}
Run Code Online (Sandbox Code Playgroud)
该\ Zend的\的mvc \应用组件本身是事件驱动的,因为它不与其他组件直接通信,而是,它只是触发事件:
/**
* 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);
}
Run Code Online (Sandbox Code Playgroud)
这是事件驱动的:您不知道将使用哪个路由器,调度程序和视图渲染器的代码,您只知道将触发这些事件.您可以挂钩几乎任何兼容的组件来监听和处理事件.组件之间没有直接通信.
但是,有一点需要注意:这是表示层(Controller + View).域层确实可以是事件驱动的,但是你看到的几乎所有应用程序都不是这种情况.**事件驱动和请求驱动之间存在混合:
// albums controller
public function indexAction()
{
return new ViewModel(array(
'albums' => $this->albumsService->getAlbumsFromArtist('Joy Division'),
));
}
Run Code Online (Sandbox Code Playgroud)
控制器组件不是事件驱动的.它直接与服务组件通信.相反,服务应该订阅由控制器上升的事件,控制器是表示层的一部分.(我将在本答案的最后指出关于什么是事件驱动的域模型的引用).
现在让我们在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);
}
Run Code Online (Sandbox Code Playgroud)
但除了具有"事件能力"之外,它还直接与ControllerResolver组件通信,因此它自请求过程开始以来并不是完全事件驱动的,尽管它触发了一些事件并允许某些组件可插拔(事实并非如此)注入为构造函数参数的ControllerResolver).
相反,要成为一个完全事件驱动的组件,它应该像在ZF2应用程序组件中一样:
// Trigger dispatch event
$result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
Run Code Online (Sandbox Code Playgroud)
我没有足够的时间来研究源代码,但起初它似乎并没有以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;
}
[...]
}
Run Code Online (Sandbox Code Playgroud)
我知道这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
... 等等
PHP不是无状态的,HTTP是.简单地说,我们基本上在无状态技术之上构建了一个层,我们可以在其上实现有状态设计.总而言之,PHP和您选择的数据存储区具有通过会话标记化基于事件驱动模式构建应用程序设计所需的所有工具.
以一种高度通用的方式,您可以将HTTP视为Web用于桌面计算的BIOS.实际上,只需稍微进一步,您就可以轻松地看到Web的隐含事件驱动特性.你说"这不是一个事件,而是一个全新的要求",我回答说,"一个全新的要求是一个事件",我的意思是在设计模式意义上的这个词.它具有与用户与应用程序交互相关的具体语义.
本质上,通过MVC和Front Controller等模式(以及HTTP cookie和PHP会话的机制),我们只需恢复会话状态,然后响应事件,相应地修改该状态.
我想考虑REST的本质:Representational State Transfer ...但我想补充一点,我们不应该忘记只有在UI事件发生时才传输状态的隐含含义.因此,我们维护与HTTP的合同,我们只在模型的"Representational States"中"说"(即文档,JSON等),但这只是我们的方言.其他系统选择在画布坐标,信号db等中说话.
所以我一直在思考它,我认为有一个概念说明了在PHP领域通过HTTP讨论这些模式时的一些模糊性:确定性.具体来说,一旦收到请求,PHP执行的路径就是确定性的,这就是为什么在PHP中考虑"事件驱动"架构非常困难的原因.我的观点是,我们应该考虑比PHP更高的一个级别,以及与用户交互的更大"会话".
在桌面计算中,我们使用runloops和状态上下文来"等待"事件.但是,我认为网络实际上是对这种架构的改进(在大多数情况下),但最终是相同的模式.我们在事件发生时引导状态,然后处理该事件,而不是runloop和无限持续时间状态.而不是仅仅将该状态保存在内存中并等待下一个事件,我们将该状态归档并关闭资源.在某种意义上它可能被认为效率较低(我们需要在每个"事件"处加载状态),但也可以被称为更高效,因为内存中永远不会出现空闲状态.我们只加载实际被消耗/操纵的状态
因此,通过这种方式,将PHP视为在宏级别上的事件驱动,同时确保任何给定的执行确实是确定性的,而实际上根本不是事件驱动的.但是,我们实现了前端控制器和MVC模式,以便我们可以为应用程序开发人员提供熟悉的甚至驱动钩子的结构.当您使用一个体面的框架时,您只需说"我想知道用户何时注册,并且当时我可以修改用户".这是事件驱动的开发.它不应该关注你框架已经为(几乎)调用你的钩子的唯一目的引导环境(相对于更传统的概念,即环境已经存在并且你只是被告知事件).这就是以事件驱动的方式开发PHP的意义.控制器(根据请求)确定发生了哪个事件,并使用它设计使用的任何机制(即观察者模式,钩子架构等),以允许您的代码处理事件,响应事件或任何命名法.最适合您的特定框架的语义.