custom/plugins/PickwareWms/vendor/pickware/api-versioning-bundle/src/ApiVersioningRequestSubscriber.php line 56

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Pickware GmbH. All rights reserved.
  4.  * This file is part of software that is released under a proprietary license.
  5.  * You must not copy, modify, distribute, make publicly available, or execute
  6.  * its contents or parts thereof without express permission by the copyright
  7.  * holder, unless otherwise permitted by law.
  8.  */
  9. declare(strict_types=1);
  10. namespace Pickware\ApiVersioningBundle;
  11. use Doctrine\Common\Annotations\Reader;
  12. use Pickware\ApiVersioningBundle\Annotation\ApiLayer as ApiLayerAnnotation;
  13. use Pickware\ApiVersioningBundle\Annotation\EntityApiLayer;
  14. use ReflectionClass;
  15. use Shopware\Core\Framework\Api\Context\AdminApiSource;
  16. use Shopware\Core\Framework\Api\Controller\ApiController;
  17. use Shopware\Core\Framework\Routing\KernelListenerPriorities;
  18. use Shopware\Core\PlatformRequest;
  19. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  20. use Symfony\Component\HttpFoundation\Request;
  21. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  22. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  23. use Symfony\Component\HttpKernel\KernelEvents;
  24. class ApiVersioningRequestSubscriber implements EventSubscriberInterface
  25. {
  26.     private const API_LAYERS_REQUEST_ATTRIBUTE 'pickware_api_layers';
  27.     private Reader $annotationReader;
  28.     private array $apiLayers = [];
  29.     public function __construct(Reader $annotationReader)
  30.     {
  31.         $this->annotationReader $annotationReader;
  32.     }
  33.     public static function getSubscribedEvents(): array
  34.     {
  35.         return [
  36.             KernelEvents::CONTROLLER => [
  37.                 'processControllerEvent',
  38.                 KernelListenerPriorities::KERNEL_CONTROLLER_EVENT_SCOPE_VALIDATE_POST,
  39.             ],
  40.             KernelEvents::RESPONSE => 'processResponseEvent',
  41.         ];
  42.     }
  43.     public function addApiLayer(ApiLayer $apiLayerstring $id): void
  44.     {
  45.         $this->apiLayers[$id] = $apiLayer;
  46.     }
  47.     public function processControllerEvent(ControllerEvent $event): void
  48.     {
  49.         $request $event->getRequest();
  50.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
  51.         if ($context === null || !($context->getSource() instanceof AdminApiSource)) {
  52.             return;
  53.         }
  54.         // Only try to find applicable api layers once the request passed basic validations to improve performance
  55.         $applicableApiLayers $this->findApplicableApiLayers($request$event->getController());
  56.         if (empty($applicableApiLayers)) {
  57.             return;
  58.         }
  59.         // Save the applicable api layers in the request to not having to determine them again when processing
  60.         // the response
  61.         $request->attributes->set(self::API_LAYERS_REQUEST_ATTRIBUTE$applicableApiLayers);
  62.         foreach ($applicableApiLayers as $apiLayer) {
  63.             $apiLayer->transformRequest($request$context);
  64.         }
  65.     }
  66.     public function processResponseEvent(ResponseEvent $event): void
  67.     {
  68.         $request $event->getRequest();
  69.         $applicableApiLayers $request->attributes->get(self::API_LAYERS_REQUEST_ATTRIBUTE);
  70.         if (empty($applicableApiLayers)) {
  71.             return;
  72.         }
  73.         // Apply the layers to the response in reverse order as they were applied to the request (newest first)
  74.         $response $event->getResponse();
  75.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
  76.         foreach (array_reverse($applicableApiLayers) as $apiLayer) {
  77.             $apiLayer->transformResponse($request$response$context);
  78.         }
  79.     }
  80.     private function findApplicableApiLayers(Request $request, callable $controllerAction): array
  81.     {
  82.         $requestVersion ApiVersion::getVersionFromRequest($request);
  83.         if ($requestVersion === null || !is_array($controllerAction)) {
  84.             return [];
  85.         }
  86.         // phpcs:ignore
  87.         [=> $controller=> $actionMethodName] = $controllerAction;
  88.         if ($controller instanceof ApiController) {
  89.             $applicableApiLayers $this->findApplicableEntityApiLayers(
  90.                 $request->attributes->get('_route'),
  91.                 $requestVersion,
  92.             );
  93.         } else {
  94.             $applicableApiLayers $this->findApplicableControllerActionApiLayers(
  95.                 $controller,
  96.                 $actionMethodName,
  97.                 $requestVersion,
  98.             );
  99.         }
  100.         // Sort all applicable layers by their version, in ascending order (oldest first)
  101.         usort(
  102.             $applicableApiLayers,
  103.             fn (ApiLayer $lhsApiLayer $rhs) => $lhs->getVersion()->compareTo($rhs->getVersion()),
  104.         );
  105.         return $applicableApiLayers;
  106.     }
  107.     private function findApplicableEntityApiLayers(string $requestRouteApiVersion $requestVersion): array
  108.     {
  109.         return array_values(array_filter(
  110.             $this->apiLayers,
  111.             function (ApiLayer $apiLayer) use ($requestRoute$requestVersion) {
  112.                 if (!$apiLayer->getVersion()->isNewerThan($requestVersion)) {
  113.                     return false;
  114.                 }
  115.                 $entityApiLayerAnnotation $this->annotationReader->getClassAnnotation(
  116.                     new ReflectionClass($apiLayer),
  117.                     EntityApiLayer::class,
  118.                 );
  119.                 if ($entityApiLayerAnnotation === null) {
  120.                     return false;
  121.                 }
  122.                 $annotationRoute sprintf(
  123.                     'api.%1$s.%2$s',
  124.                     $entityApiLayerAnnotation->entity,
  125.                     $entityApiLayerAnnotation->method,
  126.                 );
  127.                 return $annotationRoute === $requestRoute;
  128.             },
  129.         ));
  130.     }
  131.     private function findApplicableControllerActionApiLayers(
  132.         $controller,
  133.         string $actionMethodName,
  134.         ApiVersion $requestVersion
  135.     ): array {
  136.         $controllerReflection = new ReflectionClass($controller);
  137.         $apiLayerAnnotation $this->annotationReader->getMethodAnnotation(
  138.             $controllerReflection->getMethod($actionMethodName),
  139.             ApiLayerAnnotation::class,
  140.         );
  141.         $annotatedApiLayerIds = ($apiLayerAnnotation !== null) ? $apiLayerAnnotation->ids : [];
  142.         return array_values(array_filter(array_map(
  143.             function (string $id) use ($requestVersion) {
  144.                 if (isset($this->apiLayers[$id]) && $this->apiLayers[$id]->getVersion()->isNewerThan($requestVersion)) {
  145.                     return $this->apiLayers[$id];
  146.                 }
  147.                 return null;
  148.             },
  149.             $annotatedApiLayerIds,
  150.         )));
  151.     }
  152. }