事件管理器(Events Manager)

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

Naming Convention

Phalcon events use namespaces to avoid naming collisions. Each component in Phalcon occupies a different event namespace and you are free to create your own as you see fit. Event names are formatted as “component:event”. For example, as Phalcon\Db occupies the “db” namespace, its “afterQuery” event’s full name is “db:afterQuery”.

When attaching event listeners to the events manager, you can use “component” to catch all events from that component (eg. “db” to catch all of the Phalcon\Db events) or “component:event” to target a specific event (eg. “db:afterQuery”).

使用示例(Usage Example)

In the following example, we will use the EventsManager to listen for the “afterQuery” event produced in a MySQL connection managed by Phalcon\Db:

  1. <?php
  2. use Phalcon\Events\Event;
  3. use Phalcon\Events\Manager as EventsManager;
  4. use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;
  5. $eventsManager = new EventsManager();
  6. $eventsManager->attach(
  7. "db:afterQuery",
  8. function (Event $event, $connection) {
  9. echo $connection->getSQLStatement();
  10. }
  11. );
  12. $connection = new DbAdapter(
  13. [
  14. "host" => "localhost",
  15. "username" => "root",
  16. "password" => "secret",
  17. "dbname" => "invo",
  18. ]
  19. );
  20. // 将$eventsManager赋值给数据库甜适配器
  21. $connection->setEventsManager($eventsManager);
  22. // 发送一个SQL命令到数据库服务器
  23. $connection->query(
  24. "SELECT * FROM products p WHERE p.status = 1"
  25. );

Now every time a query is executed, the SQL statement will be echoed out. 第一个传递给事件侦听者的参数包含了关于正在运行事件的上下文信息,第二个则是连接本身。 A third parameter may also be specified which will contain arbitrary data specific to the event.

You must explicitly set the Events Manager to a component using the setEventsManager() method in order for that component to trigger events. You can create a new Events Manager instance for each component or you can set the same Events Manager to multiple components as the naming convention will avoid conflicts.

Instead of using lambda functions, you can use event listener classes instead. Event listeners also allow you to listen to multiple events. 作为些示例的一部分,我们同样实现了 Phalcon\Db\Profiler 来检测SQL语句是否超出了期望的执行时间:

  1. <?php
  2. use Phalcon\Db\Profiler;
  3. use Phalcon\Events\Event;
  4. use Phalcon\Logger;
  5. use Phalcon\Logger\Adapter\File;
  6. class MyDbListener
  7. {
  8. protected $_profiler;
  9. protected $_logger;
  10. /**
  11. *创建分析器并开始纪录
  12. */
  13. public function __construct()
  14. {
  15. $this->_profiler = new Profiler();
  16. $this->_logger = new Logger("../apps/logs/db.log");
  17. }
  18. /**
  19. * 如果事件触发器是'beforeQuery',此函数将会被执行
  20. */
  21. public function beforeQuery(Event $event, $connection)
  22. {
  23. $this->_profiler->startProfile(
  24. $connection->getSQLStatement()
  25. );
  26. }
  27. /**
  28. * 如果事件触发器是'afterQuery',此函数将会被执行
  29. */
  30. public function afterQuery(Event $event, $connection)
  31. {
  32. $this->_logger->log(
  33. $connection->getSQLStatement(),
  34. Logger::INFO
  35. );
  36. $this->_profiler->stopProfile();
  37. }
  38. public function getProfiler()
  39. {
  40. return $this->_profiler;
  41. }
  42. }

Attaching an event listener to the events manager is as simple as:

  1. <?php
  2. // 创建一个数据库侦听
  3. $dbListener = new MyDbListener();
  4. // 侦听全部数据库事件
  5. $eventsManager->attach(
  6. "db",
  7. $dbListener
  8. );

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

  1. <?php
  2. // 发送一个SQL命令到数据库服务器
  3. $connection->execute(
  4. "SELECT * FROM products p WHERE p.status = 1"
  5. );
  6. foreach ($dbListener->getProfiler()->getProfiles() as $profile) {
  7. echo "SQL语句: ", $profile->getSQLStatement(), "\n";
  8. echo "开始时间: ", $profile->getInitialTime(), "\n";
  9. echo "结束时间: ", $profile->getFinalTime(), "\n";
  10. echo "总共执行的时间: ", $profile->getTotalElapsedSeconds(), "\n";
  11. }

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

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

  1. <?php
  2. use Phalcon\Events\ManagerInterface;
  3. use Phalcon\Events\EventsAwareInterface;
  4. class MyComponent implements EventsAwareInterface
  5. {
  6. protected $_eventsManager;
  7. public function setEventsManager(ManagerInterface $eventsManager)
  8. {
  9. $this->_eventsManager = $eventsManager;
  10. }
  11. public function getEventsManager()
  12. {
  13. return $this->_eventsManager;
  14. }
  15. public function someTask()
  16. {
  17. $this->_eventsManager->fire("my-component:beforeSomeTask", $this);
  18. // 做一些你想做的事情
  19. echo "这里, someTask\n";
  20. $this->_eventsManager->fire("my-component:afterSomeTask", $this);
  21. }
  22. }

Notice that in this example, we’re using the “my-component” event namespace. 现在让我们来为这个组件创建一个侦听者:

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

现在让我们把全部的东西整合起来:

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. // 创建一个事件管理器
  4. $eventsManager = new EventsManager();
  5. // 创建MyComponent实例
  6. $myComponent = new MyComponent();
  7. // 将事件管理器绑定到创建MyComponent实例实例
  8. $myComponent->setEventsManager($eventsManager);
  9. // 为事件管理器附上侦听者
  10. $eventsManager->attach(
  11. "my-component",
  12. new SomeListener()
  13. );
  14. // 执行组件的方法
  15. $myComponent->someTask();

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

  1. 这里, beforeSomeTask
  2. 这里, someTask
  3. 这里, afterSomeTask

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

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

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

  1. <?php
  2. use Phalcon\Events\Event;
  3. // 从第三个参数接收数据
  4. $eventsManager->attach(
  5. "my-component",
  6. function (Event $event, $component, $data) {
  7. print_r($data);
  8. }
  9. );
  10. // 从事件上下文中接收数据
  11. $eventsManager->attach(
  12. "my-component",
  13. function (Event $event, $component) {
  14. print_r($event->getData());
  15. }
  16. );

Using Services From The DI

By extending Phalcon\Mvc\User\Plugin, you can access services from the DI, just like you would in a controller:

  1. <?php
  2. use Phalcon\Events\Event;
  3. use Phalcon\Mvc\User\Plugin;
  4. class SomeListener extends Plugin
  5. {
  6. public function beforeSomeTask(Event $event, $myComponent)
  7. {
  8. echo "Here, beforeSomeTask\n";
  9. $this->logger->debug(
  10. "beforeSomeTask has been triggered";
  11. );
  12. }
  13. public function afterSomeTask(Event $event, $myComponent)
  14. {
  15. echo "Here, afterSomeTask\n";
  16. $this->logger->debug(
  17. "afterSomeTask has been triggered";
  18. );
  19. }
  20. }

事件传播与取消(Event Propagation/Cancellation)

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

  1. <?php
  2. use Phalcon\Events\Event;
  3. $eventsManager->attach(
  4. "db",
  5. function (Event $event, $connection) {
  6. // 如果可以取消,我们就终止此事件
  7. if ($event->isCancelable()) {
  8. // 终止事件,这样的话其他侦听都就不会再收到此通知
  9. $event->stop();
  10. }
  11. // ...
  12. }
  13. );

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

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

侦听器优先级(Listener Priorities)

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

  1. <?php
  2. $eventsManager->enablePriorities(true);
  3. $eventsManager->attach("db", new DbListener(), 150); // 高优先级
  4. $eventsManager->attach("db", new DbListener(), 100); // 正常优先级
  5. $eventsManager->attach("db", new DbListener(), 50); // 低优先级

收集响应(Collecting Responses)

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

  1. <?php
  2. use Phalcon\Events\Manager as EventsManager;
  3. $eventsManager = new EventsManager();
  4. // 建立事件管理器以为收集结果响应
  5. $eventsManager->collectResponses(true);
  6. // 附上一个侦听者
  7. $eventsManager->attach(
  8. "custom:custom",
  9. function () {
  10. return "first response";
  11. }
  12. );
  13. // 附上一个侦听者
  14. $eventsManager->attach(
  15. "custom:custom",
  16. function () {
  17. return "second response";
  18. }
  19. );
  20. // 执行fire事件
  21. $eventsManager->fire("custom:custom", null);
  22. // 获取全部收集到的响应
  23. print_r($eventsManager->getResponses());

上面示例将输出:

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

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

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