Skip to content

Commit a496779

Browse files
committed
Stop extending from the SensioFrameworkExtraBundle's Template annotation class
1 parent e01be81 commit a496779

File tree

11 files changed

+236
-50
lines changed

11 files changed

+236
-50
lines changed

Controller/Annotations/View.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@
1111

1212
namespace FOS\RestBundle\Controller\Annotations;
1313

14-
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
15-
1614
/**
1715
* View annotation class.
1816
*
1917
* @Annotation
2018
* @Target({"METHOD","CLASS"})
2119
*/
2220
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
23-
class View extends Template
21+
class View
2422
{
23+
/**
24+
* The controller (+action) this annotation is set to.
25+
*
26+
* @var array
27+
*/
28+
protected $owner = [];
29+
2530
/**
2631
* @var int|null
2732
*/
@@ -42,21 +47,33 @@ class View extends Template
4247
*/
4348
public function __construct(
4449
$data = [],
45-
array $vars = [],
46-
bool $isStreamable = false,
4750
array $owner = [],
4851
?int $statusCode = null,
4952
array $serializerGroups = [],
5053
bool $serializerEnableMaxDepthChecks = false
5154
) {
52-
parent::__construct($data, $vars, $isStreamable, $owner);
53-
5455
$values = is_array($data) ? $data : [];
56+
$this->owner = $values['owner'] ?? $owner;
5557
$this->statusCode = $values['statusCode'] ?? $statusCode;
5658
$this->serializerGroups = $values['serializerGroups'] ?? $serializerGroups;
5759
$this->serializerEnableMaxDepthChecks = $values['serializerEnableMaxDepthChecks'] ?? $serializerEnableMaxDepthChecks;
5860
}
5961

62+
public function setOwner(array $owner)
63+
{
64+
$this->owner = $owner;
65+
}
66+
67+
/**
68+
* The controller (+action) this annotation is attached to.
69+
*
70+
* @return array
71+
*/
72+
public function getOwner()
73+
{
74+
return $this->owner;
75+
}
76+
6077
/**
6178
* @param int $statusCode
6279
*/

EventListener/ViewResponseListener.php

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,112 @@
1111

1212
namespace FOS\RestBundle\EventListener;
1313

14+
use Doctrine\Common\Annotations\Reader;
15+
use Doctrine\Persistence\Proxy;
1416
use FOS\RestBundle\Controller\Annotations\View as ViewAnnotation;
1517
use FOS\RestBundle\FOSRestBundle;
1618
use FOS\RestBundle\View\View;
1719
use FOS\RestBundle\View\ViewHandlerInterface;
1820
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1921
use Symfony\Component\HttpFoundation\Response;
22+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
2023
use Symfony\Component\HttpKernel\Event\ViewEvent;
2124
use Symfony\Component\HttpKernel\KernelEvents;
2225

2326
/**
24-
* The ViewResponseListener class handles the View core event as well as the "@extra:Template" annotation.
27+
* The ViewResponseListener class handles the kernel.view event and creates a {@see Response} for a {@see View} provided by the controller result.
2528
*
2629
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
2730
*
2831
* @internal
2932
*/
3033
class ViewResponseListener implements EventSubscriberInterface
3134
{
35+
/**
36+
* @var ViewHandlerInterface
37+
*/
3238
private $viewHandler;
39+
3340
private $forceView;
3441

35-
public function __construct(ViewHandlerInterface $viewHandler, bool $forceView)
42+
/**
43+
* @var Reader|null
44+
*/
45+
private $annotationReader;
46+
47+
public function __construct(ViewHandlerInterface $viewHandler, bool $forceView, ?Reader $annotationReader = null)
3648
{
3749
$this->viewHandler = $viewHandler;
3850
$this->forceView = $forceView;
51+
$this->annotationReader = $annotationReader;
52+
}
53+
54+
/**
55+
* Extracts configuration for a {@see ViewAnnotation} from the controller if present.
56+
*/
57+
public function onKernelController(ControllerEvent $event)
58+
{
59+
$request = $event->getRequest();
60+
61+
if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
62+
return;
63+
}
64+
65+
$controller = $event->getController();
66+
67+
if (!\is_array($controller) && method_exists($controller, '__invoke')) {
68+
$controller = [$controller, '__invoke'];
69+
}
70+
71+
if (!\is_array($controller)) {
72+
return;
73+
}
74+
75+
$className = $this->getRealClass(\get_class($controller[0]));
76+
$object = new \ReflectionClass($className);
77+
$method = $object->getMethod($controller[1]);
78+
79+
/** @var ViewAnnotation|null $classConfiguration */
80+
$classConfiguration = null;
81+
82+
/** @var ViewAnnotation|null $methodConfiguration */
83+
$methodConfiguration = null;
84+
85+
if (null !== $this->annotationReader) {
86+
$classConfiguration = $this->getViewConfiguration($this->annotationReader->getClassAnnotations($object));
87+
$methodConfiguration = $this->getViewConfiguration($this->annotationReader->getMethodAnnotations($method));
88+
}
89+
90+
if (80000 <= \PHP_VERSION_ID) {
91+
if (null === $classConfiguration) {
92+
$classAttributes = array_map(
93+
function (\ReflectionAttribute $attribute) {
94+
return $attribute->newInstance();
95+
},
96+
$object->getAttributes(ViewAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF)
97+
);
98+
99+
$classConfiguration = $this->getViewConfiguration($classAttributes);
100+
}
101+
102+
if (null === $methodConfiguration) {
103+
$methodAttributes = array_map(
104+
function (\ReflectionAttribute $attribute) {
105+
return $attribute->newInstance();
106+
},
107+
$method->getAttributes(ViewAnnotation::class, \ReflectionAttribute::IS_INSTANCEOF)
108+
);
109+
110+
$methodConfiguration = $this->getViewConfiguration($methodAttributes);
111+
}
112+
}
113+
114+
// An annotation/attribute on the method takes precedence over the class level
115+
if (null !== $methodConfiguration) {
116+
$request->attributes->set(FOSRestBundle::VIEW_ATTRIBUTE, $methodConfiguration);
117+
} elseif (null !== $classConfiguration) {
118+
$request->attributes->set(FOSRestBundle::VIEW_ATTRIBUTE, $classConfiguration);
119+
}
39120
}
40121

41122
public function onKernelView(ViewEvent $event): void
@@ -46,7 +127,8 @@ public function onKernelView(ViewEvent $event): void
46127
return;
47128
}
48129

49-
$configuration = $request->attributes->get('_template');
130+
/** @var ViewAnnotation|null $configuration */
131+
$configuration = $request->attributes->get(FOSRestBundle::VIEW_ATTRIBUTE);
50132

51133
$view = $event->getControllerResult();
52134
if (!$view instanceof View) {
@@ -81,16 +163,49 @@ public function onKernelView(ViewEvent $event): void
81163
$view->setFormat($request->getRequestFormat());
82164
}
83165

84-
$response = $this->viewHandler->handle($view, $request);
85-
86-
$event->setResponse($response);
166+
$event->setResponse($this->viewHandler->handle($view, $request));
87167
}
88168

89169
public static function getSubscribedEvents(): array
90170
{
91-
// Must be executed before SensioFrameworkExtraBundle's listener
92171
return [
93-
KernelEvents::VIEW => ['onKernelView', 30],
172+
KernelEvents::CONTROLLER => 'onKernelController',
173+
KernelEvents::VIEW => ['onKernelView', -128],
94174
];
95175
}
176+
177+
/**
178+
* @param object[] $annotations
179+
*/
180+
private function getViewConfiguration(array $annotations): ?ViewAnnotation
181+
{
182+
$viewAnnotation = null;
183+
184+
foreach ($annotations as $annotation) {
185+
if (!$annotation instanceof ViewAnnotation) {
186+
continue;
187+
}
188+
189+
if (null === $viewAnnotation) {
190+
$viewAnnotation = $annotation;
191+
} else {
192+
throw new \LogicException('Multiple "view" annotations are not allowed.');
193+
}
194+
}
195+
196+
return $viewAnnotation;
197+
}
198+
199+
private function getRealClass(string $class): string
200+
{
201+
if (class_exists(Proxy::class)) {
202+
if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) {
203+
return $class;
204+
}
205+
206+
return substr($class, $pos + Proxy::MARKER_LENGTH + 2);
207+
}
208+
209+
return $class;
210+
}
96211
}

FOSRestBundle.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828
class FOSRestBundle extends Bundle
2929
{
30+
const VIEW_ATTRIBUTE = '_fos_rest_view';
3031
const ZONE_ATTRIBUTE = '_fos_rest_zone';
3132

3233
/**

Resources/config/view_response_listener.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<tag name="kernel.event_subscriber" />
1111
<argument type="service" id="fos_rest.view_handler" />
1212
<argument /> <!-- force view -->
13+
<argument type="service" id="annotation_reader" on-invalid="null"/>
1314
</service>
1415

1516
</services>

0 commit comments

Comments
 (0)