Events
An event is a message saying "this just happened" that any code can react to. Request handling in Laminas is a chain of such events: match the route, call the controller, render the view script, send the response. A listener — a function that runs at that moment — can be attached to every step.
Request stages
| Event | When it happens |
|---|---|
MvcEvent::EVENT_BOOTSTRAP | The application is assembled |
MvcEvent::EVENT_ROUTE | The route is being matched |
MvcEvent::EVENT_DISPATCH | The controller is called |
MvcEvent::EVENT_DISPATCH_ERROR | An error: no route or an exception |
MvcEvent::EVENT_RENDER | The view script is rendered |
MvcEvent::EVENT_FINISH | The response is ready to be sent |
The first listener
Listeners are attached in the onBootstrap() method of the module class — the file module/Blog/src/Module.php. Laminas calls this method on every request:
// module/Blog/src/Module.php
use Laminas\Mvc\MvcEvent;
public function onBootstrap(MvcEvent $event): void
{
$events = $event->getApplication()->getEventManager();
$events->attach(MvcEvent::EVENT_FINISH, function (MvcEvent $e) {
$e->getResponse()->getHeaders()->addHeaderLine('X-Frame-Options', 'DENY');
});
}The attach() method takes an event name and a function. Every response of the site now carries the X-Frame-Options header: you can see it in the network tab of the browser's developer tools.
What a listener can reach
A listener receives an MvcEvent object with the request data:
| Method | What it returns |
|---|---|
getRequest(), getResponse() | The request and the response |
getRouteMatch() | The matched route and its parameters |
getApplication()->getServiceManager() | The service container |
getParam('exception') | The exception (in the error event) |
Priority
The third argument of attach() is the priority. The higher it is, the earlier the listener is called; the default is 1.
$events->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $e) {
$exception = $e->getParam('exception');
if ($exception !== null) {
error_log($exception->getMessage());
}
}, 100);A listener with priority 100 writes the exception text to the PHP log before the built-in listener shows the error page.
Custom events
A service can announce what happened without knowing who will react. Create a service that saves a post and triggers an event:
// module/Blog/src/Service/PostService.php
namespace Blog\Service;
use Blog\Model\Post;
use Blog\Model\PostTable;
use Laminas\EventManager\EventManagerInterface;
class PostService
{
public function __construct(
private PostTable $posts,
private EventManagerInterface $events,
) {}
public function create(Post $post): void
{
$this->posts->save($post);
$this->events->trigger('post.created', $this, ['post' => $post]);
}
}The factory gives the service an event manager of its own and tags it with the class name:
// module/Blog/src/Service/PostServiceFactory.php
public function __invoke(ContainerInterface $container): PostService
{
$events = $container->get('EventManager');
$events->setIdentifiers([PostService::class]);
return new PostService($container->get(PostTable::class), $events);
}You subscribe to such an event through the shared manager — in onBootstrap():
$shared = $events->getSharedManager();
$shared->attach(PostService::class, 'post.created', function ($e) {
error_log('New post: ' . $e->getParam('post')->title);
});The first argument is the source tag, the second the event name. If the add action calls $this->postService->create($post) instead of $this->posts->save($post), a line appears in the log after every new post. Sending mail or clearing a cache is added with another listener of the same kind, without touching the service.
Common mistake
Listeners from onBootstrap() run on every request and for every page of the site. A database query or file reading inside them slows the whole application down. Keep listeners short, and fetch services from the container inside the listener function: then they are created only when they are really needed.