Symfony事件调度器的使用

meShell · 2018年01月06日 · 阅读 747

调度器组件提供允许应用程序通过调度事件并收听事件来相互通信。在使用面向对象开发时,我们把类的职责分配的很单一,程序也因此变得很灵活,其他开发人员可以通过继承来修改其行为。但是这些变更如果要通知其它的开发人员,开发人员也有自己实现的子类,那么代码继承就是一个很大的问题,系统会因此变得复杂。这时候我们就可以通过绑定或者订阅事件来通知变更。

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整个过程的事例。

  1. 建立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里面内容很简单。

<?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,控制器的代码非常简单。


<?php

namespace Hummer\Controllers;

class Index 
{
    public function index()
    {
        return "Hello world";
    }
}

浏览器执行后的结果:

example.png

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

  1. request

    我们设置不允许GET请求.

    • 代码
      php
      $app->on(Hummer\Event::REQUEST, function($event) {
      if ($_SERVER['REQUEST_METHOD'] === 'GET') {
      $event->setResponse('不允许GET请求');
      }
      });
    • 效果

      request.png

  2. controller

    给方法强制加上后缀Action. 在控制器里面加上一个indexAction方法。当然这里我们需要注释request事件.

    • 代码

      $app->on(Event::CONTROLLER, function($event) {
          $event->setMethod(
              $event->getMethod . 'Action'
          );
      });
      public function indexAction()
      {
          return "Hellow world Action";
      }
    • 效果

      controller.png

  3. response

    我们将返回结果全部转成大写.

    • 代码

      $app->on(Event::RESPONSE, function($event) {
          $event->setResponse(
              strtoupper($event->getResponse())
          );
      });
    • 效果

      response.png

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

事件订阅

其实事件订阅和监听一样,事件先订阅再执行监听。要使用事件订阅必须先实现`EventSubscriberInterface`接口。

比如我们需要request事件。我们将Application.php添加一个订阅方法,实现一个request订阅类, 在index.php添加订阅.

//Application.php

/**
 * 订阅事件
 */
public function subscriber(EventSubscriberInterface $subscriber)
{
    $this->dispatcher->addSubscriber($subscriber);
}
<?php
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());

...

测试订阅结果

subscriber.png

整个组件的学习就此结束了通过上面的事例相们大家也明白了,本教程的代码全部在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

关于作者

全栈工程师

文章被阅读 41.6k
获赞 3