The JMSSerializerBundle has a VersionExclusionStrategy, which allows you to serialize/deserialize objects for a specific version of your API. You can mark the properties that are available for different versions using the @Since and @Until annotations:

use JMS\SerializerBundle\Annotation\Type;
use JMS\SerializerBundle\Annotation\Since;
use JMS\SerializerBundle\Annotation\Until;

class Comment
{
    /**
     * @Type("DateTime")
     * @Since("1.2.0")
     */
    private $createdAt;

    /**
     * @Type("DateTime")
     * @Until("2.1.3")
     */
    private $updatedAt;
}

The only thing you have to do is tell the serializer which version to use, before you start using it:

$this->get('serializer')->setVersion('2.0.2');

Vendor MIME types

Many webservices allow clients to request data for a specific version of the API, by sending the prefered version number as part of the Accept header of the request, like

Accept: application/vnd.matthias-v2.0.1+xml

It would be nice if we could extract this version number from the Accept header and immediately parse it to the serializer, by calling it's setVersion() method.

The ApiVersionListener

We can accomplish this in a few steps. The right moment to look at the request headers and make some preparations before any controller gets called, is when the event kernel.request is fired. We should listen to that event, inspect the Accept header, extract the requested version from the MIME type and finally call setVersion() on the serializer:


namespace Matthias\RestBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use JMS\SerializerBundle\Serializer\Serializer; class ApiVersionListener { private $serializer; public function setSerializer(Serializer $serializer) { $this->serializer = $serializer; } public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); $acceptedMimeType = $request->headers->get('Accept'); // look for: application/vnd.matthias-v{version}+xml $versionAndFormat = str_replace('application/vnd.matthias', '', $acceptedMimeType); if (preg_match('/(\-v[0-9\.]+)?\+xml/', $versionAndFormat, $matches)) { $version = str_replace('-v', '', $matches[1]); $this->serializer->setVersion($version); } } }

Next, add a service for the listener and make sure the serializer from the JMSSerializerBundle will be set by calling setSerializer().

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="matthias_rest.api_version_listener" class="Matthias\RestBundle\EventListener\ApiVersionListener">
            <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
            <call method="setSerializer">
                <argument type="service" id="serializer" />
            </call>
        </service>
    </services>
</container>

And we're done!

Suggestions

Though this is still quite a simple implementation, we would soon want to enhance the ApiVersionListener a bit. You might want to store the version as a request attribute (for instance "_api_version"), for later reference. Or make the vendor name configurable. Or provide a default version that should be used. It would also be nice (and not difficult) to support one other common and even more elegant way of specifying versions in MIME types:

Accept: application/vnd.matthias+xml; version=2.0.1
PHP Symfony2 annotations events request serializer service container