src/Storefront/Framework/Routing/Router.php line 51

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Routing;
  3. use Shopware\Core\Framework\Log\Package;
  4. use Shopware\Core\PlatformRequest;
  5. use Symfony\Bundle\FrameworkBundle\Routing\Router as SymfonyRouter;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\RequestStack;
  8. use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
  9. use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
  10. use Symfony\Component\Routing\RequestContext;
  11. use Symfony\Component\Routing\RouteCollection;
  12. use Symfony\Component\Routing\RouterInterface;
  13. use Symfony\Contracts\Service\ServiceSubscriberInterface;
  14. #[Package('storefront')]
  15. class Router implements RouterInterfaceRequestMatcherInterfaceWarmableInterfaceServiceSubscriberInterface
  16. {
  17.     /**
  18.      * @var int Used to indicate the router that we only need the path info without the sales channel prefix
  19.      */
  20.     final public const PATH_INFO 10;
  21.     /**
  22.      * @internal
  23.      */
  24.     public function __construct(private readonly SymfonyRouter $decorated, private readonly RequestStack $requestStack)
  25.     {
  26.     }
  27.     /**
  28.      * @return array<string, string>
  29.      */
  30.     public static function getSubscribedServices(): array
  31.     {
  32.         return SymfonyRouter::getSubscribedServices();
  33.     }
  34.     /**
  35.      * @return array<string>
  36.      */
  37.     public function warmUp(string $cacheDir): array
  38.     {
  39.         return $this->decorated->warmUp($cacheDir);
  40.     }
  41.     public function matchRequest(Request $request): array
  42.     {
  43.         if (!$request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID)) {
  44.             return $this->decorated->matchRequest($request);
  45.         }
  46.         $server array_merge(
  47.             $request->server->all(),
  48.             ['REQUEST_URI' => $request->attributes->get(RequestTransformer::SALES_CHANNEL_RESOLVED_URI)]
  49.         );
  50.         $localClone $request->duplicate(nullnullnullnullnull$server);
  51.         return $this->decorated->matchRequest($localClone);
  52.     }
  53.     public function setContext(RequestContext $context): void
  54.     {
  55.         $this->decorated->setContext($context);
  56.     }
  57.     public function getContext(): RequestContext
  58.     {
  59.         return $this->decorated->getContext();
  60.     }
  61.     public function getRouteCollection(): RouteCollection
  62.     {
  63.         return $this->decorated->getRouteCollection();
  64.     }
  65.     public function generate(string $name, array $parameters = [], int $referenceType self::ABSOLUTE_PATH): string
  66.     {
  67.         $basePath $this->getBasePath();
  68.         if ($referenceType === self::PATH_INFO) {
  69.             $route $this->decorated->generate($name$parameters);
  70.             return $this->removePrefix($route$basePath);
  71.         }
  72.         if (!$this->isStorefrontRoute($name)) {
  73.             return $this->decorated->generate($name$parameters$referenceType);
  74.         }
  75.         $salesChannelBaseUrl $this->getSalesChannelBaseUrl();
  76.         // we need to insert the sales channel base url between the baseUrl and the infoPath
  77.         switch ($referenceType) {
  78.             case self::NETWORK_PATH:
  79.             case self::ABSOLUTE_URL:
  80.                 $schema '';
  81.                 if ($referenceType === self::ABSOLUTE_URL) {
  82.                     $schema $this->getContext()->getScheme() . ':';
  83.                 }
  84.                 $schemaAuthority $schema '//' $this->getContext()->getHost();
  85.                 if ($this->getContext()->getHttpPort() !== 80) {
  86.                     $schemaAuthority .= ':' $this->getContext()->getHttpPort();
  87.                 } elseif ($this->getContext()->getHttpsPort() !== 443) {
  88.                     $schemaAuthority .= ':' $this->getContext()->getHttpsPort();
  89.                 }
  90.                 $generated $this->decorated->generate($name$parameters);
  91.                 $pathInfo $this->removePrefix($generated$basePath);
  92.                 $rewrite $schemaAuthority rtrim($basePath'/') . rtrim($salesChannelBaseUrl'/') . $pathInfo;
  93.                 break;
  94.             case self::RELATIVE_PATH:
  95.                 // remove base path from generated url (/shopware/public or /)
  96.                 $generated $this->removePrefix(
  97.                     $this->decorated->generate($name$parametersself::RELATIVE_PATH),
  98.                     $basePath
  99.                 );
  100.                 // url contains the base path and the base url
  101.                     // base url /shopware/public/de
  102.                 $rewrite ltrim($salesChannelBaseUrl'/') . $generated;
  103.                 break;
  104.             case self::ABSOLUTE_PATH:
  105.             default:
  106.                 $generated $this->removePrefix(
  107.                     $this->decorated->generate($name$parameters),
  108.                     $basePath
  109.                 );
  110.                 $rewrite $basePath rtrim($salesChannelBaseUrl'/') . $generated;
  111.                 break;
  112.         }
  113.         return $rewrite;
  114.     }
  115.     public function match(string $pathinfo): array
  116.     {
  117.         return $this->decorated->match($pathinfo);
  118.     }
  119.     private function removePrefix(string $subjectstring $prefix): string
  120.     {
  121.         if (!$prefix || mb_strpos($subject$prefix) !== 0) {
  122.             return $subject;
  123.         }
  124.         return mb_substr($subjectmb_strlen($prefix));
  125.     }
  126.     private function getSalesChannelBaseUrl(): string
  127.     {
  128.         $request $this->requestStack->getMainRequest();
  129.         if (!$request) {
  130.             return '';
  131.         }
  132.         $url = (string) $request->attributes->get(RequestTransformer::SALES_CHANNEL_BASE_URL);
  133.         if (empty($url)) {
  134.             return $url;
  135.         }
  136.         return '/' trim($url'/') . '/';
  137.     }
  138.     private function getBasePath(): string
  139.     {
  140.         $request $this->requestStack->getMainRequest();
  141.         if (!$request) {
  142.             return '';
  143.         }
  144.         return $request->getBasePath();
  145.     }
  146.     private function isStorefrontRoute(string $name): bool
  147.     {
  148.         return str_starts_with($name'frontend.')
  149.             || str_starts_with($name'widgets.')
  150.             || str_starts_with($name'payment.');
  151.     }
  152. }