共计 4344 个字符,预计需要花费 11 分钟才能阅读完成。
调度器组件提供允许应用程序通过调度事件并收听事件来相互通信。在使用面向对象开发时,我们把类的职责分配的很单一,程序也因此变得很灵活,其他开发人员可以通过继承来修改其行为。但是这些变更如果要通知其它的开发人员,开发人员也有自己实现的子类,那么代码继承就是一个很大的问题,系统会因此变得复杂。这时候我们就可以通过绑定或者订阅事件来通知变更。
Installation
使用 Composer 进行安装.
[root@meShell#] composer install symfony/event-dispatcher
Usage
Events
当一个事件被调度时,它由一个唯一的名字(例如 event.register)来标识,任何数量的监听器都可能正在监听。一个 Event 实例也被创建并传递给所有的监听器。Event 对象本身通常包含有关被调度的事件的数据。
命名约定
事件名称可以是任何字符串,但可以选择遵循几个简单的命名约定。
- 只能使用小写字母,数字,点 (.) 和下划线(_)
- 前缀名称后面跟着一个点(
order.
,user.*
) - 带有动词的结尾名称,指示已采取的操作
事件对象
调度程序通知监听器时,会将实际的 Event 对象传递给这些监听器。基本的 Event 类非常简单:它包含一个停止事件传播的方法,但不包含其他内容。也可以通过继承该对象实现自己的事件对象。
调度器
调度器是事件调度系统的核心对象。一般来说,创建一个调度器,它维护一个监听器的注册表。当通过调度器分派事件时,它通知所有注册了该事件的监听者。
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
监听
通过调度器的 addListener
方法来监听一个事件,方法第一个参数是事件名第二个是事件回调,第三个是事件优先级。
$listener = new RequestListener();
$dispatcher->addListener(\'request.before\', array($listener, \'onBeforeAction\'));
分发事件
通过调度器的 dispatch
方法来分发,方法第一个参数是事件名称,第二个参数是事件对象。
$listener = new RequestListener();
$requestEvent = new RequestEvent();
$dispatcher->dispatch(\'request.before\', $requestEvent);
Note: 分发的事件对象会传递给监听的回调方法 (除非这个事件冒泡已停止) 就像面上面的 onBeforeAction 回调。
回调函数通过事件参数对象来进行程序的通信,而非是回调函数的返回值, 回调函数的返回将不起任何任用。
事例
下面我们调度器,实现一个 http 请求的 request
, controller
, view
, response
整个过程的事例。
- 建立 Appliaction.php 该类的功能就是初化调度器,完成
request
,controller
,view
,response
调度功能,输出response
内容显示到浏览器中。我们定义一个事件对象里面有这几个事件常量。我们在run
分别逐步触发这几个事件。我们最终目地是从事件中取回response
内容(通信是通过事件对象), 分别说下这四个事件的用处。request
比如一个请求需要ajax
请求,这个时候我们就可以在request
事件判断是不是ajax
请求.controller
比如取到了控制器和方法,我们需要给方法加上后缀Action
.view
渲染的时候替换目录路径(这可能看起是多此一举).response
在请求完成之后,我们需要对内容统一处理比如转成json
, 设置头内容.- 整个 Application 代码
namespace Hummer {use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\Event AS EventAlias; class Event { const REQUEST = \'request\'; const RESPONSE = \'response\'; const VIEW = \'VIEW\'; const CONTROLLER = \'controller\'; } class Application { protected $dispatcher = null; public function __construct() { $this->dispatcher = new EventDispatcher();} public function run() { $event = new GetResponseEvent(); // 触发请求事件 $this->dispatcher->dispatch(Event::REQUEST, $event); // 检测事件的 response 有没有被设置,比如不允许 GET 请求 if ($event->hasResponse()) {return $this->finishResponse($event->getResponse()); } // 获取控制器,这里也可以用 path_info 来设置,比如 /index/index 等等,这里我们就不涉及路由这个了 $controller = $_GET[\'c\'] ?? null; if (empty($controller)) {throw new \Exception("Undefined controller"); } // 通过事件解析 controller, 控制器规则:hummer.controllers.index:index 或 index:index(前面是命名空间) $event = new ControllerEvent($controller); $this->dispatcher->dispatch(Event::CONTROLLER, $event); $controller = $event->getController(); $method = $event->getMethod(); // 执行方法 $response = call_user_func([new $controller, $method]); // 执行视图事件 if (is_object($response)) {$event = new ViewResponseEvent($response); $this->dispatcher->dispatch(Event::VIEW, $event); $response = $event->getResponse();} // 执行 response 完成事件 return $this->finishResponse($response); } // 执行完成事件 private function finishResponse($response) { $event = new FinishResponseEvent($response); $this->dispatcher->dispatch(Event::RESPONSE, $event); return $event->getResponse();} // 监听事件 public function on($name, $callback, $sort = 0) { $this->dispatcher->addListener($name, $callback, $sort); } } }
看代码可以看到我们能过
run
方法完成了整个流程. 里面有很多细节处理这里我们只管事件调度的用法,其它的功能我们就不多说了。
事件代码
- request 该对象是获取
response
内容,设置response
内容.class GetResponseEvent extends EventAlias { protected $response = null; public function __construct() { } public function setResponse($response) { $this->response = $response; } public function getResponse() { return $this->response; } public function hasResponse() { return $this->response !== null; } }
- controller 该对象主要是解析控制器和方法,设置方法.
class ControllerEvent extends EventAlias { private $controllerSchema = null; private $controller = null; private $method = null; public function __construct($controllerSchema) { $this->controllerSchema = $controllerSchema; list($controller, $method) = explode(\':\', $this->controllerSchema); $this->setMethod($method); foreach (explode(\'.\', $controller) as $namespaceController) {$this->controller .= "\\" . ucfirst($namespaceController); } } public function getController() { return $this->controller; } public function getMethod() { return $this->method; } public function setMethod($method) { $this->method = $method; } }
- view 和 response 两个对象都是继承了
GetResponseEvent
.class FinishResponseEvent extends GetResponseEvent { public function __construct($response) { $this->setResponse($response); } } class ViewResponseEvent extends GetResponseEvent { public function __construct($response) { $this->setResponse($response); } }
测试示例
这里我们建立一个 index.php
里面内容很简单。
require __DIR__ . \'/vendor/autoload.php\';
$app = new Hummer\Application();
try {echo $app->run();} catch(Exception $e) {echo $e->getMessage();}
能过 ControllerEvent
我们知道控制器规则。
使用的测试地址:http://example.de/dispatcher/index.php?c=hummer.controllers.index:index, 控制器的代码非常简单。
namespace Hummer\Controllers;
class Index
{
public function index()
{
return "Hello world";
}
}
浏览器执行后的结果:
这个结果是我们预期想要的结果,我们给 index.php
加上几个事件看看。
- request 我们设置不允许 GET 请求.
- 代码
$app->on(Hummer\Event::REQUEST, function($event) {
if ($_SERVER[\'REQUEST_METHOD\'] === \'GET\') {
$event->setResponse(\'不允许 GET 请求 \');
}
}); - 效果
- 代码
- controller 给方法强制加上后缀
Action
. 在控制器里面加上一个indexAction
方法。当然这里我们需要注释request
事件.- 代码
$app->on(Event::CONTROLLER, function($event) { $event->setMethod($event->getMethod . \'Action\' ); });
public function indexAction() { return "Hellow world Action"; }
- 效果
- 代码
- response 我们将返回结果全部转成大写.
- 代码
$app->on(Event::RESPONSE, function($event) { $event->setResponse(strtoupper($event->getResponse()) ); });
- 效果
- 代码
通过这个示例是不是明白了事件调度器的用法和流程,大部分框架都会使用事件调度器,下面我们学习下事件订阅。
事件订阅
其实事件订阅和监听一样,事件先订阅再执行监听。要使用事件订阅必须先实现 `EventSubscriberInterface` 接口。
比如我们需要 request
事件。我们将 Application.php
添加一个订阅方法,实现一个 request
订阅类, 在 index.php
添加订阅.
//Application.php
/**
* 订阅事件
*/
public function subscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
namespace Hummer;
namespace Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RequestListener implements EventSubscriberInterface
{
public function onRequestEvent($event)
{
echo "我是订阅的事件";
}
/**
* 实现方法
*/
public static function getSubscribedEvents()
{
return [Event::REQUEST => ["onRequestEvent", -100]
];
}
}
// 在 index.php 添订阅
...
$app->subscriber(new RequestListener());
...
测试订阅结果
整个组件的学习就此结束了通过上面的事例相们大家也明白了,本教程的代码全部在 github 上面.
https://github.com/TianLiangZhou/loocode-example/tree/master/dispatcher
推荐阅读