<?php
namespace Lightbulb\Symfony\EventSubscriber;
use Lightbulb\Symfony\Controller\AbstractApiController;
use Lightbulb\Symfony\Controller\RequiredInterface\FormContentTypeRequiredInterface;
use Lightbulb\Symfony\Controller\RequiredInterface\JsonContentTypeRequiredInterface;
use Lightbulb\Symfony\Controller\RequiredInterface\JsonRequestRequiredInterface;
use Lightbulb\Symfony\Exception\ApiErrorException;
use Lightbulb\Symfony\HttpFoundation\Response\NotAcceptableResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class ApiRequestPreProcessSubscriber implements EventSubscriberInterface
{
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}
/**
* @param ControllerEvent $event
*
* @return void
*
* @throws ApiErrorException
*/
public function onKernelController(ControllerEvent $event): void
{
$controller = $event->getController();
// when a controller class defines multiple action methods, the controller
// is returned as [$controllerInstance, 'methodName']
if (is_array($controller)) {
$controller = $controller[0];
}
if ($controller instanceof AbstractApiController) {
$this->checkRequirements($controller, $event->getRequest());
}
}
/**
* @param AbstractController $controller
* @param Request $request
*
* @return void
*
* @throws ApiErrorException
*/
private function checkRequirements(AbstractController $controller, Request $request): void
{
$this->checkControllerForRequirement(
$controller,
$request,
JsonRequestRequiredInterface::class,
'Accept',
['json', 'application/json'],
['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
);
$this->checkControllerForRequirement(
$controller,
$request,
JsonContentTypeRequiredInterface::class,
'Content-type',
['json', 'application/json'],
['POST', 'PUT', 'PATCH'],
true
);
$this->checkControllerForRequirement(
$controller,
$request,
FormContentTypeRequiredInterface::class,
'Content-type',
['multipart/form-data'],
['POST', 'PUT', 'PATCH'],
true
);
}
/**
* @param AbstractController $controller
* @param Request $request
* @param string $interface
* @param string $headerType
* @param array $contentTypes
* @param array $applicableMethods
* @param bool $requestMustHaveContent
*
* @return void
*
* @throws ApiErrorException
*/
private function checkControllerForRequirement(
AbstractController $controller,
Request $request,
string $interface,
string $headerType,
array $contentTypes,
array $applicableMethods,
bool $requestMustHaveContent = false
): void {
if ($controller instanceof $interface && in_array($request->getMethod(), $applicableMethods)) {
if (true === $requestMustHaveContent && true === empty($request->getContent())) {
$validContentType = true;
} else {
$validContentType = false;
foreach ($contentTypes as $contentType) {
if (
$request->headers->has($headerType) &&
false !== strpos($request->headers->get($headerType), $contentType)
) {
$validContentType = true;
break;
}
}
}
if (false === $validContentType) {
$message = sprintf(
'The API call needs to have a valid %s header (%s)! %s was given!',
$headerType,
implode(', ', $contentTypes),
$request->headers->get($headerType) ?? 'Nothing'
);
throw new ApiErrorException(new NotAcceptableResponse($message));
}
}
}
}