1111
1212namespace FOS \RestBundle \EventListener ;
1313
14+ use Doctrine \Common \Annotations \Reader ;
15+ use Doctrine \Persistence \Proxy ;
1416use FOS \RestBundle \Controller \Annotations \View as ViewAnnotation ;
1517use FOS \RestBundle \FOSRestBundle ;
1618use FOS \RestBundle \View \View ;
1719use FOS \RestBundle \View \ViewHandlerInterface ;
1820use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
1921use Symfony \Component \HttpFoundation \Response ;
22+ use Symfony \Component \HttpKernel \Event \ControllerEvent ;
2023use Symfony \Component \HttpKernel \Event \ViewEvent ;
2124use 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 <[email protected] > 2730 *
2831 * @internal
2932 */
3033class 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}
0 commit comments