vendor/friendsofsymfony/rest-bundle/View/ViewHandler.php line 140

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the FOSRestBundle package.
  4. *
  5. * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace FOS\RestBundle\View;
  11. use FOS\RestBundle\Context\Context;
  12. use FOS\RestBundle\Serializer\Serializer;
  13. use Symfony\Component\Form\FormInterface;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\RequestStack;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
  18. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  19. /**
  20. * View may be used in controllers to build up a response in a format agnostic way
  21. * The View class takes care of encoding your data in json, xml via the Serializer
  22. * component.
  23. *
  24. * @author Jordi Boggiano <j.boggiano@seld.be>
  25. * @author Lukas K. Smith <smith@pooteeweet.org>
  26. */
  27. final class ViewHandler implements ConfigurableViewHandlerInterface
  28. {
  29. /**
  30. * Key format, value a callable that returns a Response instance.
  31. *
  32. * @var array
  33. */
  34. private $customHandlers = [];
  35. /**
  36. * The supported formats as keys.
  37. *
  38. * @var array
  39. */
  40. private $formats;
  41. private $failedValidationCode;
  42. private $emptyContentCode;
  43. private $serializeNull;
  44. private $exclusionStrategyGroups = [];
  45. private $exclusionStrategyVersion;
  46. private $serializeNullStrategy;
  47. private $urlGenerator;
  48. private $serializer;
  49. private $requestStack;
  50. private $options;
  51. private function __construct(
  52. UrlGeneratorInterface $urlGenerator,
  53. Serializer $serializer,
  54. RequestStack $requestStack,
  55. array $formats = null,
  56. int $failedValidationCode = Response::HTTP_BAD_REQUEST,
  57. int $emptyContentCode = Response::HTTP_NO_CONTENT,
  58. bool $serializeNull = false,
  59. array $options = []
  60. ) {
  61. $this->urlGenerator = $urlGenerator;
  62. $this->serializer = $serializer;
  63. $this->requestStack = $requestStack;
  64. $this->formats = (array) $formats;
  65. $this->failedValidationCode = $failedValidationCode;
  66. $this->emptyContentCode = $emptyContentCode;
  67. $this->serializeNull = $serializeNull;
  68. $this->options = $options + [
  69. 'exclusionStrategyGroups' => [],
  70. 'exclusionStrategyVersion' => null,
  71. 'serializeNullStrategy' => null,
  72. ];
  73. $this->reset();
  74. }
  75. public static function create(
  76. UrlGeneratorInterface $urlGenerator,
  77. Serializer $serializer,
  78. RequestStack $requestStack,
  79. array $formats = null,
  80. int $failedValidationCode = Response::HTTP_BAD_REQUEST,
  81. int $emptyContentCode = Response::HTTP_NO_CONTENT,
  82. bool $serializeNull = false,
  83. array $options = []
  84. ): self {
  85. return new self($urlGenerator, $serializer, $requestStack, $formats, $failedValidationCode, $emptyContentCode, $serializeNull, $options);
  86. }
  87. /**
  88. * @param string[]|string $groups
  89. */
  90. public function setExclusionStrategyGroups($groups): void
  91. {
  92. $this->exclusionStrategyGroups = (array) $groups;
  93. }
  94. public function setExclusionStrategyVersion(string $version): void
  95. {
  96. $this->exclusionStrategyVersion = $version;
  97. }
  98. public function setSerializeNullStrategy(bool $isEnabled): void
  99. {
  100. $this->serializeNullStrategy = $isEnabled;
  101. }
  102. /**
  103. * {@inheritdoc}
  104. */
  105. public function supports(string $format): bool
  106. {
  107. return isset($this->customHandlers[$format]) || isset($this->formats[$format]);
  108. }
  109. /**
  110. * Registers a custom handler.
  111. *
  112. * The handler must have the following signature: handler(ViewHandler $viewHandler, View $view, Request $request, $format)
  113. * It can use the public methods of this class to retrieve the needed data and return a
  114. * Response object ready to be sent.
  115. */
  116. public function registerHandler(string $format, callable $callable): void
  117. {
  118. $this->customHandlers[$format] = $callable;
  119. }
  120. /**
  121. * Handles a request with the proper handler.
  122. *
  123. * Decides on which handler to use based on the request format.
  124. *
  125. * @throws UnsupportedMediaTypeHttpException
  126. */
  127. public function handle(View $view, Request $request = null): Response
  128. {
  129. if (null === $request) {
  130. $request = $this->requestStack->getCurrentRequest();
  131. }
  132. $format = $view->getFormat() ?: $request->getRequestFormat();
  133. if (!$this->supports($format)) {
  134. $msg = "Format '$format' not supported, handler must be implemented";
  135. throw new UnsupportedMediaTypeHttpException($msg);
  136. }
  137. if (isset($this->customHandlers[$format])) {
  138. return call_user_func($this->customHandlers[$format], $this, $view, $request, $format);
  139. }
  140. return $this->createResponse($view, $request, $format);
  141. }
  142. public function createRedirectResponse(View $view, string $location, string $format): Response
  143. {
  144. $content = null;
  145. if ((Response::HTTP_CREATED === $view->getStatusCode() || Response::HTTP_ACCEPTED === $view->getStatusCode()) && null !== $view->getData()) {
  146. $response = $this->initResponse($view, $format);
  147. } else {
  148. $response = $view->getResponse();
  149. }
  150. $code = $this->getStatusCode($view, $content);
  151. $response->setStatusCode($code);
  152. $response->headers->set('Location', $location);
  153. return $response;
  154. }
  155. public function createResponse(View $view, Request $request, string $format): Response
  156. {
  157. $route = $view->getRoute();
  158. $location = $route
  159. ? $this->urlGenerator->generate($route, (array) $view->getRouteParameters(), UrlGeneratorInterface::ABSOLUTE_URL)
  160. : $view->getLocation();
  161. if ($location) {
  162. return $this->createRedirectResponse($view, $location, $format);
  163. }
  164. $response = $this->initResponse($view, $format);
  165. if (!$response->headers->has('Content-Type')) {
  166. $mimeType = $request->attributes->get('media_type');
  167. if (null === $mimeType) {
  168. $mimeType = $request->getMimeType($format);
  169. }
  170. $response->headers->set('Content-Type', $mimeType);
  171. }
  172. return $response;
  173. }
  174. /**
  175. * Gets a response HTTP status code from a View instance.
  176. *
  177. * By default it will return 200. However if there is a FormInterface stored for
  178. * the key 'form' in the View's data it will return the failed_validation
  179. * configuration if the form instance has errors.
  180. *
  181. * @param string|false|null
  182. */
  183. private function getStatusCode(View $view, $content = null): int
  184. {
  185. $form = $this->getFormFromView($view);
  186. if (null !== $form && $form->isSubmitted() && !$form->isValid()) {
  187. return $this->failedValidationCode;
  188. }
  189. $statusCode = $view->getStatusCode();
  190. if (null !== $statusCode) {
  191. return $statusCode;
  192. }
  193. return null !== $content ? Response::HTTP_OK : $this->emptyContentCode;
  194. }
  195. private function getSerializationContext(View $view): Context
  196. {
  197. $context = $view->getContext();
  198. $groups = $context->getGroups();
  199. if (empty($groups) && $this->exclusionStrategyGroups) {
  200. $context->setGroups($this->exclusionStrategyGroups);
  201. }
  202. if (null === $context->getVersion() && $this->exclusionStrategyVersion) {
  203. $context->setVersion($this->exclusionStrategyVersion);
  204. }
  205. if (null === $context->getSerializeNull() && null !== $this->serializeNullStrategy) {
  206. $context->setSerializeNull($this->serializeNullStrategy);
  207. }
  208. if (null !== $view->getStatusCode() && !$context->hasAttribute('status_code')) {
  209. $context->setAttribute('status_code', $view->getStatusCode());
  210. }
  211. return $context;
  212. }
  213. private function initResponse(View $view, string $format): Response
  214. {
  215. $content = null;
  216. if ($this->serializeNull || null !== $view->getData()) {
  217. $data = $this->getDataFromView($view);
  218. if ($data instanceof FormInterface && $data->isSubmitted() && !$data->isValid()) {
  219. $view->getContext()->setAttribute('status_code', $this->failedValidationCode);
  220. }
  221. $context = $this->getSerializationContext($view);
  222. $content = $this->serializer->serialize($data, $format, $context);
  223. }
  224. $response = $view->getResponse();
  225. $response->setStatusCode($this->getStatusCode($view, $content));
  226. if (null !== $content) {
  227. $response->setContent($content);
  228. }
  229. return $response;
  230. }
  231. private function getFormFromView(View $view): ?FormInterface
  232. {
  233. $data = $view->getData();
  234. if ($data instanceof FormInterface) {
  235. return $data;
  236. }
  237. if (is_array($data) && isset($data['form']) && $data['form'] instanceof FormInterface) {
  238. return $data['form'];
  239. }
  240. return null;
  241. }
  242. private function getDataFromView(View $view)
  243. {
  244. $form = $this->getFormFromView($view);
  245. if (null === $form) {
  246. return $view->getData();
  247. }
  248. return $form;
  249. }
  250. public function reset(): void
  251. {
  252. $this->exclusionStrategyGroups = $this->options['exclusionStrategyGroups'];
  253. $this->exclusionStrategyVersion = $this->options['exclusionStrategyVersion'];
  254. $this->serializeNullStrategy = $this->options['serializeNullStrategy'];
  255. }
  256. }