This article will show how to use Symfony2 framework to version an api using the Accept Header, the expression language, an event listener and routing conditions.
In order to see the theory behind this, please read this excellent article: The ultimate solution versioning rest api: Content Negotiation
Step 1: Create the ApiVersionListener
ApiVersionListener listens to kernel.request event and uses Symfony2 AcceptHeader class to extract the version attribute from the Accept header. If a version is found, then the version value is set as a request attribute, so that it can be used later on routing conditions.
The Accept header should have the following format in order for the ApiVersionListener to work:
Accept: application/vnd.custom.api+json;version=1
<?php
// src/AppBundle/EventListener/ApiVersionListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\AcceptHeader;
class ApiVersionListener
{
/**
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
return;
}
$request = $event->getRequest();
$acceptHeader = AcceptHeader::fromString($request->headers->get('Accept'))->get('application/vnd.custom.api+json');
if (!is_null($acceptHeader)) {
$version = $acceptHeader->getAttribute('version');
$request->attributes->set('version', $version);
}
}
}
Step 2: Register ApiVersionListener as a service
Put the following in AppBundle/Resources/config/services.yml:
kernel.listener.api_version_listener:
class: AppBundle\EventListener\ApiVersionListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 100 }
The most important thing in the above configuration is the priority. We need to make sure that this listener is triggered before the Router listener (before the route is matched). As you can see in kernel.request documentation, Router listener has priority 32, so if we set priority 100 (higher priority is triggered first) for ApiVersionListener we will not face any problems.
Step 3: Routing magic
Now using the route conditions and the expression language we can do the following:
get_customer_v1:
path: /api/customer/{id}
defaults: { _controller: AppBundle:CustomerV1:GetClient }
condition: "request.attributes.get('version') == 1"
get_customer_v2:
path: /api/customer/{id}
defaults: { _controller: AppBundle:CustomerV2:GetClient }
condition: "request.attributes.get('version') == 2"
so if the request includes the following header
Accept: application/vnd.custom.api+json;version=1
then AppBundle:CustomerV1Controller with handle it.
If it includes
Accept: application/vnd.custom.api+json;version=2
it will be handled by AppBundle:CustomerV2Controller, otherwise the router will not match a route and a 404 response will be returned.
That’s it. The only thing I do not know is if it possible to force the router to respond with HTTP Error 406 (Not Acceptable) instead of 404. Any ideas?
Also please let me know if you know any other, more efficient way to version an api using the Accept Header.
Thanks for reading
Additional resources used: