vendor/api-platform/core/src/Symfony/EventListener/SerializeListener.php line 60

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.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. declare(strict_types=1);
  11. namespace ApiPlatform\Symfony\EventListener;
  12. use ApiPlatform\Doctrine\Odm\State\Options as ODMOptions;
  13. use ApiPlatform\Doctrine\Orm\State\Options;
  14. use ApiPlatform\Exception\RuntimeException;
  15. use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
  16. use ApiPlatform\Serializer\ResourceList;
  17. use ApiPlatform\Serializer\SerializerContextBuilderInterface;
  18. use ApiPlatform\State\Util\OperationRequestInitiatorTrait;
  19. use ApiPlatform\Symfony\Util\RequestAttributesExtractor;
  20. use ApiPlatform\Util\ErrorFormatGuesser;
  21. use ApiPlatform\Validator\Exception\ValidationException;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpKernel\Event\ViewEvent;
  25. use Symfony\Component\Serializer\Encoder\EncoderInterface;
  26. use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
  27. use Symfony\Component\Serializer\SerializerInterface;
  28. use Symfony\Component\WebLink\GenericLinkProvider;
  29. use Symfony\Component\WebLink\Link;
  30. /**
  31.  * Serializes data.
  32.  *
  33.  * @author Kévin Dunglas <dunglas@gmail.com>
  34.  */
  35. final class SerializeListener
  36. {
  37.     use OperationRequestInitiatorTrait;
  38.     public const OPERATION_ATTRIBUTE_KEY 'serialize';
  39.     public function __construct(
  40.         private readonly SerializerInterface $serializer,
  41.         private readonly SerializerContextBuilderInterface $serializerContextBuilder,
  42.         ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory null,
  43.         private readonly array $errorFormats = [],
  44.         // @phpstan-ignore-next-line we don't need this anymore
  45.         private readonly bool $debug false,
  46.     ) {
  47.         $this->resourceMetadataCollectionFactory $resourceMetadataFactory;
  48.     }
  49.     /**
  50.      * Serializes the data to the requested format.
  51.      */
  52.     public function onKernelView(ViewEvent $event): void
  53.     {
  54.         $controllerResult $event->getControllerResult();
  55.         $request $event->getRequest();
  56.         if ($controllerResult instanceof Response) {
  57.             return;
  58.         }
  59.         $attributes RequestAttributesExtractor::extractAttributes($request);
  60.         if (!($attributes['respond'] ?? $request->attributes->getBoolean('_api_respond'false))) {
  61.             return;
  62.         }
  63.         $operation $this->initializeOperation($request);
  64.         if ('api_platform.symfony.main_controller' === $operation?->getController() || $request->attributes->get('_api_platform_disable_listeners')) {
  65.             return;
  66.         }
  67.         if (!($operation?->canSerialize() ?? true)) {
  68.             return;
  69.         }
  70.         if (!$attributes) {
  71.             $this->serializeRawData($event$request$controllerResult);
  72.             return;
  73.         }
  74.         $context $this->serializerContextBuilder->createFromRequest($requesttrue$attributes);
  75.         if (isset($context['output']) && \array_key_exists('class'$context['output']) && null === $context['output']['class']) {
  76.             $event->setControllerResult(null);
  77.             return;
  78.         }
  79.         if ($controllerResult instanceof ValidationException) {
  80.             $format ErrorFormatGuesser::guessErrorFormat($request$this->errorFormats);
  81.             $previousOperation $request->attributes->get('_api_previous_operation');
  82.             if (!($previousOperation?->getExtraProperties()['rfc_7807_compliant_errors'] ?? false)) {
  83.                 $context['groups'] = ['legacy_'.$format['key']];
  84.                 $context['force_iri_generation'] = false;
  85.             }
  86.         }
  87.         if ($included $request->attributes->get('_api_included')) {
  88.             $context['api_included'] = $included;
  89.         }
  90.         $resources = new ResourceList();
  91.         $context['resources'] = &$resources;
  92.         $context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY][] = 'resources';
  93.         $resourcesToPush = new ResourceList();
  94.         $context['resources_to_push'] = &$resourcesToPush;
  95.         $context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY][] = 'resources_to_push';
  96.         if (($options $operation?->getStateOptions()) && (
  97.             ($options instanceof Options && $options->getEntityClass())
  98.             || ($options instanceof ODMOptions && $options->getDocumentClass())
  99.         )) {
  100.             $context['force_resource_class'] = $operation->getClass();
  101.         }
  102.         $request->attributes->set('_api_normalization_context'$context);
  103.         $event->setControllerResult($this->serializer->serialize($controllerResult$request->getRequestFormat(), $context));
  104.         $request->attributes->set('_resources'$request->attributes->get('_resources', []) + (array) $resources);
  105.         if (!\count($resourcesToPush)) {
  106.             return;
  107.         }
  108.         $linkProvider $request->attributes->get('_api_platform_links', new GenericLinkProvider());
  109.         foreach ($resourcesToPush as $resourceToPush) {
  110.             $linkProvider $linkProvider->withLink((new Link('preload'$resourceToPush))->withAttribute('as''fetch'));
  111.         }
  112.         $request->attributes->set('_api_platform_links'$linkProvider);
  113.     }
  114.     /**
  115.      * Tries to serialize data that are not API resources (e.g. the entrypoint or data returned by a custom controller).
  116.      *
  117.      * @throws RuntimeException
  118.      */
  119.     private function serializeRawData(ViewEvent $eventRequest $request$controllerResult): void
  120.     {
  121.         if (\is_object($controllerResult)) {
  122.             $event->setControllerResult($this->serializer->serialize($controllerResult$request->getRequestFormat(), $request->attributes->get('_api_normalization_context', [])));
  123.             return;
  124.         }
  125.         if (!$this->serializer instanceof EncoderInterface) {
  126.             throw new RuntimeException(sprintf('The serializer must implement the "%s" interface.'EncoderInterface::class));
  127.         }
  128.         $event->setControllerResult($this->serializer->encode($controllerResult$request->getRequestFormat()));
  129.     }
  130. }