Events Manager


Event Manager - 图1

Overview

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

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. // Assign the eventsManager to the db adapter instance
  21. $connection->setEventsManager($eventsManager);
  22. // Send a SQL command to the database server
  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. The first parameter passed to the lambda function contains contextual information about the event that is running, the second is the source of the event (in this case the connection itself). 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. In this example, we will 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\Events\Event;
  4. use Phalcon\Logger;
  5. use Phalcon\Logger\Adapter\File;
  6. class MyDbListener
  7. {
  8. protected $profiler;
  9. protected $logger;
  10. /**
  11. * Creates the profiler and starts the logging
  12. */
  13. public function __construct()
  14. {
  15. $this->profiler = new Profiler();
  16. $this->logger = new Logger('../apps/logs/db.log');
  17. }
  18. /**
  19. * This is executed if the event triggered is 'beforeQuery'
  20. */
  21. public function beforeQuery(Event $event, $connection)
  22. {
  23. $this->profiler->startProfile(
  24. $connection->getSQLStatement()
  25. );
  26. }
  27. /**
  28. * This is executed if the event triggered is '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. // Create a database listener
  3. $dbListener = new MyDbListener();
  4. // Listen all the database events
  5. $eventsManager->attach(
  6. 'db',
  7. $dbListener
  8. );

The resulting profile data can be obtained from the listener:

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

Creating components that trigger Events

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 (it implements Phalcon\Events\EventsAwareInterface); when its someTask() method is executed it triggers two events to any listener in the EventsManager:

  1. <?php
  2. use Phalcon\Events\EventsAwareInterface;
  3. use Phalcon\Events\ManagerInterface;
  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. // Do some task
  19. echo 'Here, 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. Now we need to create an event listener for this component:

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

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(
  11. 'my-component',
  12. new SomeListener()
  13. );
  14. // Execute methods in the component
  15. $myComponent->someTask();

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

  1. Here, beforeSomeTask
  2. Here, someTask
  3. Here, afterSomeTask

Additional data may also be 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. use Phalcon\Events\Event;
  3. // Receiving the data in the third parameter
  4. $eventsManager->attach(
  5. 'my-component',
  6. function (Event $event, $component, $data) {
  7. print_r($data);
  8. }
  9. );
  10. // Receiving the data from the event context
  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\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\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

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 from being notified about the event:

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

By default, events are cancelable - even most of the 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. $eventsManager->enablePriorities(true);
  3. $eventsManager->attach('db', new DbListener(), 150); // More priority
  4. $eventsManager->attach('db', new DbListener(), 100); // Normal priority
  5. $eventsManager->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. $eventsManager = new EventsManager();
  4. // Set up the events manager to collect responses
  5. $eventsManager->collectResponses(true);
  6. // Attach a listener
  7. $eventsManager->attach(
  8. 'custom:custom',
  9. function () {
  10. return 'first response';
  11. }
  12. );
  13. // Attach a listener
  14. $eventsManager->attach(
  15. 'custom:custom',
  16. function () {
  17. return 'second response';
  18. }
  19. );
  20. // Fire the event
  21. $eventsManager->fire('custom:custom', null);
  22. // Get all the collected responses
  23. print_r($eventsManager->getResponses());

The above example produces:

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

Implementing your own EventsManager

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

List of Events

The events available in Phalcon are:

ComponentEvent
ACLacl:afterCheckAccess
ACLacl:beforeCheckAccess
Applicationapplication:afterHandleRequest
Applicationapplication:afterStartModule
Applicationapplication:beforeHandleRequest
Applicationapplication:beforeSendResponse
Applicationapplication:beforeStartModule
Applicationapplication:boot
Applicationapplication:viewRender
CLIdispatch:beforeException
CollectionafterCreate
CollectionafterSave
CollectionafterUpdate
CollectionafterValidation
CollectionafterValidationOnCreate
CollectionafterValidationOnUpdate
CollectionbeforeCreate
CollectionbeforeSave
CollectionbeforeUpdate
CollectionbeforeValidation
CollectionbeforeValidationOnCreate
CollectionbeforeValidationOnUpdate
CollectionnotDeleted
CollectionnotSave
CollectionnotSaved
CollectiononValidationFails
Collectionvalidation
Collection ManagercollectionManager:afterInitialize
Consoleconsole:afterHandleTask
Consoleconsole:afterStartModule
Consoleconsole:beforeHandleTask
Consoleconsole:beforeStartModule
Dbdb:afterQuery
Dbdb:beforeQuery
Dbdb:beginTransaction
Dbdb:createSavepoint
Dbdb:commitTransaction
Dbdb:releaseSavepoint
Dbdb:rollbackTransaction
Dbdb:rollbackSavepoint
Dispatcherdispatch:afterExecuteRoute
Dispatcherdispatch:afterDispatch
Dispatcherdispatch:afterDispatchLoop
Dispatcherdispatch:afterInitialize
Dispatcherdispatch:beforeException
Dispatcherdispatch:beforeExecuteRoute
Dispatcherdispatch:beforeDispatch
Dispatcherdispatch:beforeDispatchLoop
Dispatcherdispatch:beforeForward
Dispatcherdispatch:beforeNotFoundAction
Loaderloader:afterCheckClass
Loaderloader:beforeCheckClass
Loaderloader:beforeCheckPath
Loaderloader:pathFound
Micromicro:afterHandleRoute
Micromicro:afterExecuteRoute
Micromicro:beforeExecuteRoute
Micromicro:beforeHandleRoute
Micromicro:beforeNotFound
MiddlewareafterBinding
MiddlewareafterExecuteRoute
MiddlewareafterHandleRoute
MiddlewarebeforeExecuteRoute
MiddlewarebeforeHandleRoute
MiddlewarebeforeNotFound
ModelafterCreate
ModelafterDelete
ModelafterSave
ModelafterUpdate
ModelafterValidation
ModelafterValidationOnCreate
ModelafterValidationOnUpdate
ModelbeforeDelete
ModelnotDeleted
ModelbeforeCreate
ModelbeforeDelete
ModelbeforeSave
ModelbeforeUpdate
ModelbeforeValidation
ModelbeforeValidationOnCreate
ModelbeforeValidationOnUpdate
ModelnotSave
ModelnotSaved
ModelonValidationFails
ModelprepareSave
Models ManagermodelsManager:afterInitialize
Requestrequest:afterAuthorizationResolve
Requestrequest:beforeAuthorizationResolve
Responseresponse:afterSendHeaders
Responseresponse:beforeSendHeaders
Routerrouter:beforeCheckRoutes
Routerrouter:beforeCheckRoute
Routerrouter:matchedRoute
Routerrouter:notMatchedRoute
Routerrouter:afterCheckRoutes
Routerrouter:beforeMount
Viewview:afterRender
Viewview:afterRenderView
Viewview:beforeRender
Viewview:beforeRenderView
Viewview:notFoundView
VoltcompileFilter
VoltcompileFunction
VoltcompileStatement
VoltresolveExpression