<?php
namespace Can\RestBundle\EventListener;
use Can\RestBundle\CanRestBundle;
use Can\RestBundle\Http\Exception\UpgradeRequiredHttpException;
use Can\RestBundle\Mapping\Metadata\VersionMetadata;
use Can\RestBundle\Mapping\MetadataStore;
use Can\RestBundle\URI\ServiceNode;
use Can\RestBundle\URI\WellKnownURIs;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use DateTime;
use Exception;
/**
* @group service
*
* @package can/rest-bundle
* @author lechecacharro <lechecacharro@gmail.com>
*/
class VersionListener
{
/**
* @var bool
*/
private $enabled;
/**
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* @var MetadataStore
*/
private $metadataStore;
/**
* VersionListener constructor.
*
* @param bool $enabled
* @param MetadataStore $metadataStore
* @param EventDispatcherInterface $dispatcher
*/
public function __construct(bool $enabled, EventDispatcherInterface $dispatcher, MetadataStore $metadataStore)
{
$this->enabled = $enabled;
$this->dispatcher = $dispatcher;
$this->metadataStore = $metadataStore;
}
/**
* @param RequestEvent $event
*
* @throws Exception
*/
public function onRequest(RequestEvent $event): void
{
if (! $this->enabled) {
return;
}
if (! $event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
if (! $serviceNode = $request->attributes->get(CanRestBundle::ATTR_SERVICE_NODE)) {
return;
}
if (! $metadata = $this->getVersionMetadata($serviceNode)) {
return;
}
// If the requested service version is no longer supported, and we can
// offer an alternative to the consumer (that is a version upgrade),
// then return a 426 (Upgrade Required) response
if ($metadata->isNotSupported()) {
if ($upgradeToVersion = $this->getUpgradeToVersion($metadata)) {
throw new UpgradeRequiredHttpException($serviceNode->getVersionNumber(), $upgradeToVersion, $metadata->getSupportEndsAt());
}
}
// Else, if the requested service version is already deprecated, then
// add a response listener to generate the appropriate HTTP headers
// to keep the consumer informed
if ($metadata->isDeprecated()) {
$this->dispatcher->addListener('kernel.response', [static::class, 'onResponse'], -1);
}
}
/**
* @param ResponseEvent $event
*/
public function onResponse(ResponseEvent $event): void
{
$serviceNode = $event->getRequest()->attributes->get(CanRestBundle::ATTR_SERVICE_NODE);
$metadata = $this->getVersionMetadata($serviceNode);
// Add a Sunset header
$event->getResponse()->headers->set('Sunset', $this->getSunset($metadata));
}
/**
* @param ServiceNode $serviceNode
*
* @return VersionMetadata | null
*/
private function getVersionMetadata(ServiceNode $serviceNode): ?VersionMetadata
{
if (! $versionNumber = $serviceNode->getVersionNumber()) {
return null;
}
if (WellKnownURIs::WELL_KNOWN === $versionNumber) {
return null;
}
return $this->metadataStore->getVersionMetadata($versionNumber);
}
/**
* @param VersionMetadata $metadata
*
* @return string
*/
private function getSunset(VersionMetadata $metadata): string
{
return $metadata->getDeprecatedSince()->format(DateTime::RFC1123);
}
/**
* @param VersionMetadata $metadata
*
* @return string | null
*
* @throws Exception
*/
private function getUpgradeToVersion(VersionMetadata $metadata): ?string
{
$serviceMetadata = $this->metadataStore->getServiceMetadata();
$versionIterator = $serviceMetadata->getVersions()->getIterator();
$versionNumber = $metadata->getNumber();
// Recommend always the LTS version
foreach ($versionIterator as $version) {
if ($version->isLongTermSupport()) {
return $version->getNumber();
}
}
// If no LTS version is declared, then choose the immediate version
// number which is not deprecated
foreach ($versionIterator as $version) {
if ($version->getNumber() > $versionNumber && ! $version->isDeprecated()) {
return $version->getNumber();
}
}
// Return the default service version, if it's different from the
// deprecated version
if ($serviceMetadata->getDefaultVersionMetadata() > $versionNumber) {
return $serviceMetadata->getDefaultVersion();
}
// Cannot find a version to upgrade to
return null;
}
}