<?php
/**
* @license SILK SOFTWARE HOUSE SP Z O O
*/
namespace App\EventSubscriber;
use App\Annotation\Idempotent;
use App\Entity\CachedResponse;
use App\Repository\CachedResponseRepository;
use Doctrine\ORM\EntityManagerInterface as EntityManager;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionException;
/**
* Class IdempotenceSubscriber.
*/
class IdempotenceSubscriber implements EventSubscriberInterface
{
/**
* Cache repository.
*
* @var CachedResponseRepository
*/
private $cache;
/**
* Annotation reader.
*
* @var Reader
*/
private $annotationReader;
/**
* IdempotenceSubscriber constructor.
*
* @param EntityManager $entityManager
* @param Reader $reader
*/
public function __construct(EntityManager $entityManager, Reader $reader)
{
$this->cache = $entityManager->getRepository(CachedResponse::class);
$this->annotationReader = $reader;
}
/**
* Get subscribed events.
*
* @return array
*/
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return [
KernelEvents::CONTROLLER => [
['onKernelController', -2],
],
KernelEvents::RESPONSE => [
['onKernelResponse', 10],
],
];
}
/**
* On kernel controller.
*
* @param ControllerEvent $event
*/
public function onKernelController(ControllerEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$annotation = $this->getIdempotentAnnotation($event->getController());
if (null === $annotation) {
return;
}
$idempotenceHash = $event->getRequest()->getContent();
$event->getRequest()->cookies->set('idempotent', $idempotenceHash);
if ($cached = $this->cache->getCachedResponse($idempotenceHash, $event->getRequest()->get('_route'))) {
$event->setController(function () use ($cached) {
return $cached;
});
}
}
/**
* On kernel response.
*
* @param ResponseEvent $event
*/
public function onKernelResponse(ResponseEvent $event)
{
$requestId = $event->getRequest()->cookies->get('idempotent');
if (!$requestId) {
return;
}
$response = $event->getResponse();
if (!$response instanceof JsonResponse || 201 !== $response->getStatusCode()) {
return;
}
$this->cache->cache(
$requestId,
$response->getStatusCode(),
$response->getContent(),
$event->getRequest()->get('_route')
);
}
/**
* Does controller have idempotent annotation.
*
* @param callable $controllerData
*
* @return Idempotent|null
*/
private function getIdempotentAnnotation($controllerData)
{
$annotation = null;
if (!is_array($controllerData)) {
return null;
}
list($controllerObject, $methodName) = $controllerData;
try {
$controller = new ReflectionClass($controllerObject);
$method = $controller->getMethod($methodName);
$annotation = $this->annotationReader->getMethodAnnotation($method, Idempotent::class);
} catch (ReflectionException $e) {
return null;
}
return $annotation instanceof Idempotent ? $annotation : null;
}
}