事件管理器Events Manager

此组件的目的是为了通过创建“钩子”拦截框架中大部分的组件操作。 这些钩子允许开发者获得状态信息,操纵数据或者改变某个组件进程中的执行流向。

The purpose of this component is to intercept the execution of most of the components of the framework by creating “hooks point”. These hook points allow the developer to obtain status information, manipulate data or change the flow of execution during the process of a component.

使用示例Usage Example

以下面示例中,我们使用EventsManager来侦听在 PhalconDb 管理下的MySQL连接中产生的事件。 首先,我们需要一个侦听者对象来完成这部分的工作。我们创建了一个类,这个类有我们需要侦听事件所对应的方法:

In the following example, we use the EventsManager to listen for events produced in a MySQL connection managed by Phalcon\Db. First, we need a listener object to do this. We created a class whose methods are the events we want to listen:

  1. <?php
  2. class MyDbListener
  3. {
  4. public function afterConnect()
  5. {
  6. }
  7. public function beforeQuery()
  8. {
  9. }
  10. public function afterQuery()
  11. {
  12. }
  13. }

这个新的类可能有点啰嗦,但我们需要这样做。 事件管理器在组件和我们的侦听类之间充当着接口角色,并提供了基于在我们侦听类中所定义方法的钩子:

This new class can be as verbose as we need it to. The EventsManager will interface between the component and our listener class, offering hook points based on the methods we defined in our listener class:

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
  4. $eventsManager = new EventsManager();
  5. //Create a database listener
  6. $dbListener = new MyDbListener();
  7. //Listen all the database events
  8. $eventsManager->attach('db', $dbListener);
  9. $connection = new DbAdapter(array(
  10. "host" => "localhost",
  11. "username" => "root",
  12. "password" => "secret",
  13. "dbname" => "invo"
  14. ));
  15. //Assign the eventsManager to the db adapter instance
  16. $connection->setEventsManager($eventsManager);
  17. //Send a SQL command to the database server
  18. $connection->query("SELECT * FROM products p WHERE p.status = 1");

为了纪录我们应用中全部执行的SQL语句,我们需要使用“afterQuery”事件。 第一个传递给事件侦听者的参数包含了关于正在运行事件的上下文信息,第二个则是连接本身。

In order to log all the SQL statements executed by our application, we need to use the event “afterQuery”. The first parameter passed to the event listener contains contextual information about the event that is running, the second is the connection itself.

  1. <?php
  2. use Phalcon\Logger\Adapter\File as Logger;
  3. class MyDbListener
  4. {
  5. protected $_logger;
  6. public function __construct()
  7. {
  8. $this->_logger = new Logger("../apps/logs/db.log");
  9. }
  10. public function afterQuery($event, $connection)
  11. {
  12. $this->_logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
  13. }
  14. }

作为些示例的一部分,我们同样实现了 Phalcon\Db\Profiler 来检测SQL语句是否超出了期望的执行时间:

As part of this example, we will also implement the Phalcon\Db\Profiler to detect the SQL statements that are taking longer to execute than expected:

  1. <?php
  2. use Phalcon\Db\Profiler;
  3. use Phalcon\Logger;
  4. use Phalcon\Logger\Adapter\File;
  5. class MyDbListener
  6. {
  7. protected $_profiler;
  8. protected $_logger;
  9. /**
  10. * Creates the profiler and starts the logging
  11. */
  12. public function __construct()
  13. {
  14. $this->_profiler = new Profiler();
  15. $this->_logger = new Logger("../apps/logs/db.log");
  16. }
  17. /**
  18. * This is executed if the event triggered is 'beforeQuery'
  19. */
  20. public function beforeQuery($event, $connection)
  21. {
  22. $this->_profiler->startProfile($connection->getSQLStatement());
  23. }
  24. /**
  25. * This is executed if the event triggered is 'afterQuery'
  26. */
  27. public function afterQuery($event, $connection)
  28. {
  29. $this->_logger->log($connection->getSQLStatement(), Logger::INFO);
  30. $this->_profiler->stopProfile();
  31. }
  32. public function getProfiler()
  33. {
  34. return $this->_profiler;
  35. }
  36. }

可以从侦听者中获取结果分析数据:

The resulting profile data can be obtained from the listener:

  1. <?php
  2. //Send a SQL command to the database server
  3. $connection->execute("SELECT * FROM products p WHERE p.status = 1");
  4. foreach ($dbListener->getProfiler()->getProfiles() as $profile) {
  5. echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
  6. echo "Start Time: ", $profile->getInitialTime(), "\n";
  7. echo "Final Time: ", $profile->getFinalTime(), "\n";
  8. echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
  9. }

类似地,我们可以注册一个匿名函数来执行这些任务,而不是再分离出一个侦听类(如上面看到的):

In a similar manner we can register an lambda function to perform the task instead of a separate listener class (as seen above):

  1. <?php
  2. //Listen all the database events
  3. $eventManager->attach('db', function($event, $connection) {
  4. if ($event->getType() == 'afterQuery') {
  5. echo $connection->getSQLStatement();
  6. }
  7. });

创建组件触发事件Creating components that trigger Events

你可以在你的应用中为事件管理器的触发事件创建组件。这样的结果是,可以有很多存在的侦听者为这些产生的事件作出响应。 在以下的示例中,我们将会创建一个叫做“MyComponent”组件。这是个意识事件管理器组件; 当它的方法“someTask”被执行时它将触发事件管理器中全部侦听者的两个事件:

You can create components in your application that trigger events to an EventsManager. As a consequence, there may exist listeners that react to these events when generated. In the following example we’re creating a component called “MyComponent”. This component is EventsManager aware; when its method “someTask” is executed it triggers two events to any listener in the EventsManager:

  1. <?php
  2. use Phalcon\Events\EventsAwareInterface;
  3. class MyComponent implements EventsAwareInterface
  4. {
  5. protected $_eventsManager;
  6. public function setEventsManager($eventsManager)
  7. {
  8. $this->_eventsManager = $eventsManager;
  9. }
  10. public function getEventsManager()
  11. {
  12. return $this->_eventsManager;
  13. }
  14. public function someTask()
  15. {
  16. $this->_eventsManager->fire("my-component:beforeSomeTask", $this);
  17. // do some task
  18. $this->_eventsManager->fire("my-component:afterSomeTask", $this);
  19. }
  20. }

注意到这个组件产生的事件都以“my-component”为前缀。这是一个唯一的关键词,可以帮助我们区分各个组件产生的事件。 你甚至可以在组件的外面生成相同名字的事件。现在让我们来为这个组件创建一个侦听者:

Note that events produced by this component are prefixed with “my-component”. This is a unique word that helps us identify events that are generated from certain component. You can even generate events outside the component with the same name. Now let’s create a listener to this component:

  1. <?php
  2. class SomeListener
  3. {
  4. public function beforeSomeTask($event, $myComponent)
  5. {
  6. echo "Here, beforeSomeTask\n";
  7. }
  8. public function afterSomeTask($event, $myComponent)
  9. {
  10. echo "Here, afterSomeTask\n";
  11. }
  12. }

侦听者可以是简单的一个实现了全部组件触发事件的类。现在让我们把全部的东西整合起来:

A listener is simply a class that implements any of all the events triggered by the component. Now let’s make everything work together:

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. //Create an Events Manager
  4. $eventsManager = new EventsManager();
  5. //Create the MyComponent instance
  6. $myComponent = new MyComponent();
  7. //Bind the eventsManager to the instance
  8. $myComponent->setEventsManager($eventsManager);
  9. //Attach the listener to the EventsManager
  10. $eventsManager->attach('my-component', new SomeListener());
  11. //Execute methods in the component
  12. $myComponent->someTask();

当“someTask”被执行时,在侦听者里面的两个方法将会被执行,并产生以下输出:

As “someTask” is executed, the two methods in the listener will be executed, producing the following output:

  1. Here, beforeSomeTask
  2. Here, afterSomeTask

当触发一个事件时也可以使用“fire”中的第三个参数来传递额外的数据:

Additional data may also passed when triggering an event using the third parameter of “fire”:

  1. <?php
  2. $eventsManager->fire("my-component:afterSomeTask", $this, $extraData);

在一个侦听者里,第三个参数可用于接收此参数:

In a listener the third parameter also receives this data:

  1. <?php
  2. //Receiving the data in the third parameter
  3. $eventManager->attach('my-component', function($event, $component, $data) {
  4. print_r($data);
  5. });
  6. //Receiving the data from the event context
  7. $eventManager->attach('my-component', function($event, $component) {
  8. print_r($event->getData());
  9. });

如果一个侦听者仅是对某个特定类型的事件感兴趣,你要吧直接附上一个侦听者:

If a listener it is only interested in listening a specific type of event you can attach a listener directly:

  1. <?php
  2. //The handler will only be executed if the event triggered is "beforeSomeTask"
  3. $eventManager->attach('my-component:beforeSomeTask', function($event, $component) {
  4. //...
  5. });

事件传播与取消Event Propagation/Cancellation

可能会有多个侦听者添加到同一个事件管理器,这意味着对于相同的事件会通知多个侦听者。 这些侦听者会以它们在事件管理器注册的顺序来通知。有些事件是可以被取消的,暗示着这些事件可以被终止以防其他侦听都再收到事件的通知:

Many listeners may be added to the same event manager, this means that for the same type of event many listeners can be notified. The listeners are notified in the order they were registered in the EventsManager. Some events are cancelable, indicating that these may be stopped preventing other listeners are notified about the event:

  1. <?php
  2. $eventsManager->attach('db', function($event, $connection){
  3. //We stop the event if it is cancelable
  4. if ($event->isCancelable()) {
  5. //Stop the event, so other listeners will not be notified about this
  6. $event->stop();
  7. }
  8. //...
  9. });

默认情况下全部的事件都是可以取消的,甚至框架提供的事件也是可以取消的。 你可以通过在fire中的第四个参数中传递false来指明这是一个不可取消的事件:

By default events are cancelable, even most of events produced by the framework are cancelables. You can fire a not-cancelable event by passing “false” in the fourth parameter of fire:

  1. <?php
  2. $eventsManager->fire("my-component:afterSomeTask", $this, $extraData, false);

侦听器优先级Listener Priorities

当附上侦听者时,你可以设置一个优先级。使用此特性,你可以指定这些侦听者被调用的固定顺序:

When attaching listeners you can set a specific priority. With this feature you can attach listeners indicating the order in which they must be called:

  1. <?php
  2. $evManager->enablePriorities(true);
  3. $evManager->attach('db', new DbListener(), 150); //More priority
  4. $evManager->attach('db', new DbListener(), 100); //Normal priority
  5. $evManager->attach('db', new DbListener(), 50); //Less priority

收集响应Collecting Responses

事件管理器可以收集每一个被通知的侦听者返回的响应,以下这个示例解释了它是如何工作的:

The events manager can collect every response returned by every notified listener, this example explains how it works:

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. $evManager = new EventsManager();
  4. //Set up the events manager to collect responses
  5. $evManager->collectResponses(true);
  6. //Attach a listener
  7. $evManager->attach('custom:custom', function() {
  8. return 'first response';
  9. });
  10. //Attach a listener
  11. $evManager->attach('custom:custom', function() {
  12. return 'second response';
  13. });
  14. //Fire the event
  15. $evManager->fire('custom:custom', null);
  16. //Get all the collected responses
  17. print_r($evManager->getResponses());

上面示例将输出:

The above example produces:

  1. Array ( [0] => first response [1] => second response )

自定义事件管理器Implementing your own EventsManager

如果想要替换Phalcon提供的事件管理器,必须实现 Phalcon\Events\ManagerInterface 中的接口。

The Phalcon\Events\ManagerInterface interface must be implemented to create your own EventsManager replacing the one provided by Phalcon.