-
Notifications
You must be signed in to change notification settings - Fork 703
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2401 from mbabker/decouple-view-from-sensio
Conditionally stop extending from the SensioFrameworkExtraBundle's Template annotation class
- Loading branch information
Showing
12 changed files
with
276 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,31 +11,112 @@ | |
|
||
namespace FOS\RestBundle\EventListener; | ||
|
||
use Doctrine\Common\Annotations\Reader; | ||
use Doctrine\Persistence\Proxy; | ||
use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation; | ||
use FOS\RestBundle\FOSRestBundle; | ||
use FOS\RestBundle\View\View; | ||
use FOS\RestBundle\View\ViewHandlerInterface; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Event\ControllerEvent; | ||
use Symfony\Component\HttpKernel\Event\ViewEvent; | ||
use Symfony\Component\HttpKernel\KernelEvents; | ||
|
||
/** | ||
* The ViewResponseListener class handles the View core event as well as the "@extra:Template" annotation. | ||
* The ViewResponseListener class handles the kernel.view event and creates a {@see Response} for a {@see View} provided by the controller result. | ||
* | ||
* @author Lukas Kahwe Smith <[email protected]> | ||
* | ||
* @internal | ||
*/ | ||
class ViewResponseListener implements EventSubscriberInterface | ||
{ | ||
/** | ||
* @var ViewHandlerInterface | ||
*/ | ||
private $viewHandler; | ||
|
||
private $forceView; | ||
|
||
public function __construct(ViewHandlerInterface $viewHandler, bool $forceView) | ||
/** | ||
* @var Reader|null | ||
*/ | ||
private $annotationReader; | ||
|
||
public function __construct(ViewHandlerInterface $viewHandler, bool $forceView, ?Reader $annotationReader = null) | ||
{ | ||
$this->viewHandler = $viewHandler; | ||
$this->forceView = $forceView; | ||
$this->annotationReader = $annotationReader; | ||
} | ||
|
||
/** | ||
* Extracts configuration for a {@see ViewAnnotation} from the controller if present. | ||
*/ | ||
public function onKernelController(ControllerEvent $event) | ||
{ | ||
$request = $event->getRequest(); | ||
|
||
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) { | ||
return; | ||
} | ||
|
||
$controller = $event->getController(); | ||
|
||
if (!\is_array($controller) && method_exists($controller, '__invoke')) { | ||
$controller = [$controller, '__invoke']; | ||
} | ||
|
||
if (!\is_array($controller)) { | ||
return; | ||
} | ||
|
||
$className = $this->getRealClass(\get_class($controller[0])); | ||
$object = new \ReflectionClass($className); | ||
$method = $object->getMethod($controller[1]); | ||
|
||
/** @var ViewAnnotation|null $classConfiguration */ | ||
$classConfiguration = null; | ||
|
||
/** @var ViewAnnotation|null $methodConfiguration */ | ||
$methodConfiguration = null; | ||
|
||
if (null !== $this->annotationReader) { | ||
$classConfiguration = $this->getViewConfiguration($this->annotationReader->getClassAnnotations($object)); | ||
$methodConfiguration = $this->getViewConfiguration($this->annotationReader->getMethodAnnotations($method)); | ||
} | ||
|
||
if (80000 <= \PHP_VERSION_ID) { | ||
if (null === $classConfiguration) { | ||
$classAttributes = array_map( | ||
function (\ReflectionAttribute $attribute) { | ||
return $attribute->newInstance(); | ||
}, | ||
$object->getAttributes(ViewAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF) | ||
); | ||
|
||
$classConfiguration = $this->getViewConfiguration($classAttributes); | ||
} | ||
|
||
if (null === $methodConfiguration) { | ||
$methodAttributes = array_map( | ||
function (\ReflectionAttribute $attribute) { | ||
return $attribute->newInstance(); | ||
}, | ||
$method->getAttributes(ViewAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF) | ||
); | ||
|
||
$methodConfiguration = $this->getViewConfiguration($methodAttributes); | ||
} | ||
} | ||
|
||
// An annotation/attribute on the method takes precedence over the class level | ||
if (null !== $methodConfiguration) { | ||
$request->attributes->set(FOSRestBundle::VIEW_ATTRIBUTE, $methodConfiguration); | ||
} elseif (null !== $classConfiguration) { | ||
$request->attributes->set(FOSRestBundle::VIEW_ATTRIBUTE, $classConfiguration); | ||
} | ||
} | ||
|
||
public function onKernelView(ViewEvent $event): void | ||
|
@@ -46,7 +127,8 @@ public function onKernelView(ViewEvent $event): void | |
return; | ||
} | ||
|
||
$configuration = $request->attributes->get('_template'); | ||
/** @var ViewAnnotation|null $configuration */ | ||
$configuration = $request->attributes->get(FOSRestBundle::VIEW_ATTRIBUTE); | ||
|
||
$view = $event->getControllerResult(); | ||
if (!$view instanceof View) { | ||
|
@@ -81,16 +163,49 @@ public function onKernelView(ViewEvent $event): void | |
$view->setFormat($request->getRequestFormat()); | ||
} | ||
|
||
$response = $this->viewHandler->handle($view, $request); | ||
|
||
$event->setResponse($response); | ||
$event->setResponse($this->viewHandler->handle($view, $request)); | ||
} | ||
|
||
public static function getSubscribedEvents(): array | ||
{ | ||
// Must be executed before SensioFrameworkExtraBundle's listener | ||
return [ | ||
KernelEvents::VIEW => ['onKernelView', 30], | ||
KernelEvents::CONTROLLER => 'onKernelController', | ||
KernelEvents::VIEW => ['onKernelView', -128], | ||
]; | ||
} | ||
|
||
/** | ||
* @param object[] $annotations | ||
*/ | ||
private function getViewConfiguration(array $annotations): ?ViewAnnotation | ||
{ | ||
$viewAnnotation = null; | ||
|
||
foreach ($annotations as $annotation) { | ||
if (!$annotation instanceof ViewAnnotation) { | ||
continue; | ||
} | ||
|
||
if (null === $viewAnnotation) { | ||
$viewAnnotation = $annotation; | ||
} else { | ||
throw new \LogicException('Multiple "view" annotations are not allowed.'); | ||
} | ||
} | ||
|
||
return $viewAnnotation; | ||
} | ||
|
||
private function getRealClass(string $class): string | ||
{ | ||
if (class_exists(Proxy::class)) { | ||
if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { | ||
return $class; | ||
} | ||
|
||
return substr($class, $pos + Proxy::MARKER_LENGTH + 2); | ||
} | ||
|
||
return $class; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.