Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

EventWhen it happens
MvcEvent::EVENT_BOOTSTRAPThe application is assembled
MvcEvent::EVENT_ROUTEThe route is being matched
MvcEvent::EVENT_DISPATCHThe controller is called
MvcEvent::EVENT_DISPATCH_ERRORAn error: no route or an exception
MvcEvent::EVENT_RENDERThe view script is rendered
MvcEvent::EVENT_FINISHThe 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:

MethodWhat 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.

Thanks for reading!

Donate to the site — we work hard for you.