Symfony事件调度器的使用

2,540次阅读

共计 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 请求的 requestcontrollerviewresponse 整个过程的事例。

  1. 建立 Appliaction.php 该类的功能就是初化调度器,完成 requestcontrollerviewresponse 调度功能,输出 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";
    }
}

浏览器执行后的结果:

Symfony 事件调度器的使用

这个结果是我们预期想要的结果,我们给 index.php 加上几个事件看看。

  1. request 我们设置不允许 GET 请求.
    • 代码
      $app->on(Hummer\Event::REQUEST, function($event) {
      if ($_SERVER[\'REQUEST_METHOD\'] === \'GET\') {
      $event->setResponse(\'不允许 GET 请求 \'); 

      });
    • 效果Symfony 事件调度器的使用
  2. controller 给方法强制加上后缀 Action. 在控制器里面加上一个indexAction 方法。当然这里我们需要注释 request 事件.
    • 代码
      $app->on(Event::CONTROLLER, function($event) {
          $event->setMethod($event->getMethod . \'Action\'
          );
      });
      public function indexAction()
      {
          return "Hellow world Action";
      }
    • 效果Symfony 事件调度器的使用
  3. response 我们将返回结果全部转成大写.
    • 代码
      $app->on(Event::RESPONSE, function($event) {
          $event->setResponse(strtoupper($event->getResponse())
          );
      });
    • 效果Symfony 事件调度器的使用

通过这个示例是不是明白了事件调度器的用法和流程,大部分框架都会使用事件调度器,下面我们学习下事件订阅。

事件订阅

其实事件订阅和监听一样,事件先订阅再执行监听。要使用事件订阅必须先实现 `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());

...

测试订阅结果

Symfony 事件调度器的使用

整个组件的学习就此结束了通过上面的事例相们大家也明白了,本教程的代码全部在 github 上面.

https://github.com/TianLiangZhou/loocode-example/tree/master/dispatcher

推荐阅读

  1. http://symfony.com/doc/current/components/event_dispatcher.html
  2. https://github.com/silexphp/Silex

正文完
 
Blood.Cold
版权声明:本站原创文章,由 Blood.Cold 2019-06-03发表,共计4344字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。