Symfony, edit the Response globally using the Kernel Response event

Edit the Symfony response globally with Kernel Response event

One of the things I like most of the Symfony framework is its Http Kernel component. Not only it does offer a very straightforward abstraction to handle requests and responses in an object-oriented way but it also allows you to interact with the whole response generation process through events.

Symfony Http Kernel component overview

This approach is very convenient and flexible and in fact the Http Kernel component is the foundation of the Symfony framework but also of several other famous frameworks (Silex, Laravel) and CMSes (Drupal, BackBee CMS).

The Kernel Response event

One event that I’ve been using a lot lately is the Kernel Response event which allows you to edit the response after it has been generated.

Thanks to this event you can easily modify the response object (cookies, headers, content, etc.) before it gets sent out to the user without affecting the specific logic of every controller thus avoiding code cluttering and duplication.

I will present two different real case scenarios to show how useful (and simple) it is.

Example 1. Add custom Http headers to notify remaining api calls

Let’s suppose we developed a wonderful rate limited api and now we want to add some custom headers to notify the user about how much he is using the API. It seems a good idea to copy the approach adopted by the GitHub APIs and add three custome headers: X-RateLimit-Limit (maximum number of requests per period), X-RateLimit-Remaining (remaining requests in the current period) and X-RateLimit-Reset (the timestamp on which the current period ends).

As I don’t want to implement a fully working solution here let’s assume we have already written a rate limit checker service registered as rate_limit_checker that implements the following interface:

interface RateLimitCheckerInterface
{
	public function getRateLimit();

	public function getRateLimitRemaining();

    public function getRateLimitReset();
}

Let’s now write our RateLimitHeadersListener:

use Symfony\Component\HttpKernel\Event\FilterResponseEvent;

final class RateLimitHeadersListener
{
	private $rateLimitChecker;

	public function __construct(
    	RateLimitCheckerInterface $rateLimitChecker
    ){
    	$this->rateLimitChecker = $rateLimitChecker;
    }

    public function onKernelResponse(FilterResponseEvent $event)
    {
        $headers = $event->getResponse()->headers;

        $headers->set(
        	'X-RateLimit-Limit',
            $this->rateLimitChecker->getRateLimit()
        );

        $headers->set(
        	'X-RateLimit-Remaining',
            $this->rateLimitChecker->getRateLimitRemaining()
        );

        $headers->set(
        	'X-RateLimit-Reset',
            $this->rateLimitChecker->getRateLimitReset()
        );
    }
}

Now we need to register the listener as a tagged service:

#services.yml
rate_limit_listener:
  class: RateLimitHeadersListener
  arguments: ['@rate_limit_checker']
  tags:
    - {
        name: kernel.event_listener,
        event: kernel.response,
        method: onKernelResponse,
      }

That’s it. Really straightforward, isn’t it? Should be clear now that, by using this event based approach, we don’t have to touch the logic of every single controller.

Take a small break and get ready to jump to another example.

Well, now imagine that we have to build an affiliate program based on referral links (I did it lately).

The general idea is that our affiliates are identified by an ID that they can attach to every url of the website as query parameter. This way every URL of our website can be an entry point for our visitors and our affiliates are free to promote the content that is more relevant for them. Doing so we need to verify every possibile request to check for the referrral parameter and keep track of the whole session of the visitor (or even better monitor him for a given amount of days) to see if his visits converts into some kind of action for which we have to reward the affiliate. To write a more formal specification we have to:

  1. Allow any of our affiliates to share links with a special referral code as query parameter: ?_ref=<REF_ID> (where REF_ID is the unique id of the affiliate).
  2. Intercept visitor referred by affiliates through the referral parameter and identify the referral
  3. Create a cookie to track the referred visitor for 30 days

Again we can use the Kernel Response Event and create a dedicated listener for this task. Before doing so suppose we have already developed a mechanism to store our affiliates and that we have coded a affiliate_repository service which implementats of the following interface:

interface AffiliateRepositoryInterface
{
	public function findOneById($id);
}

Now let’s write our listener to intercept clicks on referral links:

final class AffiliateLinkClickListener
{
	private $affiliateRepository;

    public function __construct(
    	AffiliateRepositoryInterface $affiliateRepository
    ){
    	$this->affiliateRepository = $affiliateRepository;
    }

    public function onKernelResponse(FilterResponseEvent $event)
    {
    	$request = $event->getRequest();
        $response = $event->getResponse();

        // 1.
        if ($request->query->has('_ref')) {
        	$affiliateId = $request->query->get('_ref');
            // 2.
            if (null !== $this->affiliateRepository->findOneById($affiliateId)) {
            	// 3.
                $cookie = new Cookie('_ref', $affiliateId, new \DateTime('+30 days'));
                $response->headers->setCookie($cookie);
            }
        }
    }
}

The code is pretty simple here:

  1. We check if there’s a _ref parameter in the current request
  2. If so we check if we have an affiliate with the id found in the _ref parameter
  3. If that’s the case we create a cookie that will allow us to keep track of the referral for 30 days.

Obviously don’t forget to register the listener as a tagged service:

#services.yml
affiliate_link_click_listener:
  class: AffiliateLinkClickListener
  arguments: ['@affiliate_repository']
  tags:
    - {
        name: kernel.event_listener,
        event: kernel.response,
        method: onKernelResponse,
      }

As I said, this mechanisms allows you to track the reference but you also need to track any conversion and check the cookie to see if it has been generated by some affiliate.

Conclusion

As in every tutorial, bear in mind that the code is just a sample to give you a general idea about an approach you can follow to solve some generic problem. You need to adapt it to your use case and consider relevant factors such as security, testing, etc.

Let me know what you think with a comment (comments) and feel free to suggest other real-life use cases for the Kernel Response Event.

Thanks!

PS: Huge thanks to @AxelLessio and @JavierEguiluz for taking the time to review my very bad english ;)

Sharing is caring!

If you got value from this article, please consider sharing it with your friends and colleagues.

Found a typo or something that can be improved?

In the spirit of Open Source, you can contribute to this article by submitting a PR on GitHub.

You might also like

Cover picture for a blog post titled Transparent pixel response with Symfony, how to track email opening

Transparent pixel response with Symfony, how to track email opening

This blog post explains how to implement email open tracking in Symfony using a transparent tracking pixel. It provides code examples for generating a tracking image response and handling the tracking logic in a controller.

Calendar Icon

Cover picture for a blog post titled 6 Rules of thumb to build blazing fast web server applications

6 Rules of thumb to build blazing fast web server applications

This post highlights 6 important rules to keep in mind when developing performant web applications: avoid premature optimization, do the minimum required work, defer non-critical tasks, leverage caching, avoid N+1 queries, and design for horizontal scaling. Following these guidelines will help you write efficient code from the start and build apps ready to handle growth.

Calendar Icon

Cover picture for a blog post titled Write a console application using Symfony and Pimple

Write a console application using Symfony and Pimple

This article shows how to build a simple command line application using the Symfony Console component and Pimple dependency injection container. It provides a step-by-step guide on structuring the code, defining services, configuring parameters and wiring everything together to create a executable console app.

Calendar Icon

Cover picture for a blog post titled Integrating Twig.js and BazingaJsTranslationBundle

Integrating Twig.js and BazingaJsTranslationBundle

The post explains how to integrate twig.js with BazingaJsTranslationBundle to handle translations consistently between PHP and JavaScript. It shows how to build a custom Twig extension to translate strings with the Bazinga Translator object and handle differences in parameter formatting.

Calendar Icon

Cover picture for a blog post titled Symfony security: authentication made simple (well, maybe!)

Symfony security: authentication made simple (well, maybe!)

This post collects resources and provides a graph to understand how Symfony authentication works behind the scenes, from the initial request to the final authenticated token. It clarifies the relationships between key classes like firewall, authentication provider and authentication listener.

Calendar Icon

Cover picture for a blog post titled 6 Tips to Build Fast Web Applications (Php Dublin March 2016 Talk)

6 Tips to Build Fast Web Applications (Php Dublin March 2016 Talk)

This post shares 6 tips to build fast web applications based on a talk at Php Dublin in March 2016. It includes slides and covers topics like caching, compression, database optimization, and more.

Calendar Icon