Symfony事件调度器的使用

  • Symfony事件调度器的使用已关闭评论
  • 278 views
  • A+
所属分类:PHP

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

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
  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin