diff --git a/composer.json b/composer.json index d893ae2..c38e29b 100644 --- a/composer.json +++ b/composer.json @@ -5,14 +5,19 @@ "license": "proprietary", "require": { "php": ">=8.2", - "spryker-shop/storage-router-extension": "^1.0.0", + "spryker-shop/storage-router-extension": "^1.1.0", "spryker/kernel": "^3.30.0", "spryker/router-extension": "^1.0.0", + "spryker/store": "^1.19.0", "spryker/symfony": "^3.2.2", "spryker/url-storage": "^1.4.1" }, "require-dev": { - "spryker/code-sniffer": "*" + "spryker/code-sniffer": "*", + "spryker/util-text": "*" + }, + "suggest": { + "spryker/util-text": "If you want to use the RouterEnhancer plugins." }, "autoload": { "psr-4": { diff --git a/src/SprykerShop/Shared/StorageRouter/StorageRouterConstants.php b/src/SprykerShop/Shared/StorageRouter/StorageRouterConstants.php new file mode 100644 index 0000000..e82a12e --- /dev/null +++ b/src/SprykerShop/Shared/StorageRouter/StorageRouterConstants.php @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/src/SprykerShop/Yves/StorageRouter/Dependency/Client/StorageRouterToStoreClientInterface.php b/src/SprykerShop/Yves/StorageRouter/Dependency/Client/StorageRouterToStoreClientInterface.php new file mode 100644 index 0000000..a4d9389 --- /dev/null +++ b/src/SprykerShop/Yves/StorageRouter/Dependency/Client/StorageRouterToStoreClientInterface.php @@ -0,0 +1,18 @@ +storeClient = $storeClient; + } + + /** + * @return \Generated\Shared\Transfer\StoreTransfer + */ + public function getCurrentStore(): StoreTransfer + { + return $this->storeClient->getCurrentStore(); + } +} diff --git a/src/SprykerShop/Yves/StorageRouter/Plugin/RouterEnhancer/StorePrefixStorageRouterEnhancerPlugin.php b/src/SprykerShop/Yves/StorageRouter/Plugin/RouterEnhancer/StorePrefixStorageRouterEnhancerPlugin.php new file mode 100644 index 0000000..1cf570f --- /dev/null +++ b/src/SprykerShop/Yves/StorageRouter/Plugin/RouterEnhancer/StorePrefixStorageRouterEnhancerPlugin.php @@ -0,0 +1,128 @@ +getConfig()->getAllowedStores(), true)) { + $this->currentStore = array_shift($pathinfoFragments); + + return '/' . implode('/', $pathinfoFragments); + } + + return $pathinfo; + } + + /** + * @param array $parameters + * @param \Symfony\Component\Routing\RequestContext $requestContext + * + * @return array + */ + public function afterMatch(array $parameters, RequestContext $requestContext): array + { + if ($this->currentStore !== null) { + $parameters[static::PARAMETER_STORE] = $this->currentStore; + } + + return $parameters; + } + + /** + * @param string $url + * @param \Symfony\Component\Routing\RequestContext $requestContext + * @param int $referenceType + * + * @return string + */ + public function afterGenerate(string $url, RequestContext $requestContext, int $referenceType): string + { + $store = $this->findStore($requestContext); + + if ($store !== null) { + return $this->buildUrlWithStore($url, $store, $referenceType); + } + + return $url; + } + + /** + * @param \Symfony\Component\Routing\RequestContext $requestContext + * + * @return string|null + */ + protected function findStore(RequestContext $requestContext): ?string + { + return $requestContext->hasParameter(static::PARAMETER_STORE) && $requestContext->getParameter(static::PARAMETER_STORE) !== null + ? $requestContext->getParameter(static::PARAMETER_STORE) + : ($this->getConfig()->isStoreRoutingEnabled() + ? $this->getFactory()->getStoreClient()->getCurrentStore()->getNameOrFail() + : null); + } + + /** + * @param string $url + * @param string $store + * @param int $referenceType + * + * @return string + */ + protected function buildUrlWithStore(string $url, string $store, int $referenceType): string + { + if ($url === '/') { + $url = ''; + } + + if ($referenceType === DynamicRouter::ABSOLUTE_PATH) { + return sprintf('/%s%s', $store, $url); + } + + if ($referenceType === DynamicRouter::ABSOLUTE_URL) { + $parsedUrl = Url::parse($url); + $pathWithStore = sprintf('/%s%s', $store, $parsedUrl->getPath()); + $parsedUrl->setPath($pathWithStore); + + return (string)$parsedUrl; + } + + return $url; + } +} diff --git a/src/SprykerShop/Yves/StorageRouter/RequestMatcher/StorageRequestMatcher.php b/src/SprykerShop/Yves/StorageRouter/RequestMatcher/StorageRequestMatcher.php index 722153a..8a8bf47 100644 --- a/src/SprykerShop/Yves/StorageRouter/RequestMatcher/StorageRequestMatcher.php +++ b/src/SprykerShop/Yves/StorageRouter/RequestMatcher/StorageRequestMatcher.php @@ -11,20 +11,35 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RequestContext; class StorageRequestMatcher implements RequestMatcherInterface { + /** + * @var string + */ + protected const ATTRIBUTE_PATH_INFO = 'pathinfo'; + /** * @var \SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToUrlStorageClientInterface */ protected $urlStorageClient; + /** + * @var array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> + */ + protected array $storageRouterEnhancerPlugins; + /** * @param \SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToUrlStorageClientInterface $urlStorageClient + * @param array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> $storageRouterEnhancerPlugins */ - public function __construct(StorageRouterToUrlStorageClientInterface $urlStorageClient) - { + public function __construct( + StorageRouterToUrlStorageClientInterface $urlStorageClient, + array $storageRouterEnhancerPlugins + ) { $this->urlStorageClient = $urlStorageClient; + $this->storageRouterEnhancerPlugins = $storageRouterEnhancerPlugins; } /** @@ -36,12 +51,23 @@ public function __construct(StorageRouterToUrlStorageClientInterface $urlStorage */ public function matchRequest(Request $request): array { + $requestContext = new RequestContext(); $pathinfo = $request->getPathInfo(); + + foreach ($this->storageRouterEnhancerPlugins as $storageRouterEnhancerPlugin) { + $pathinfo = $storageRouterEnhancerPlugin->beforeMatch($pathinfo, $requestContext); + } + if ($pathinfo !== '/') { $localeName = $request->attributes->get('_locale'); $urlDetails = $this->urlStorageClient->matchUrl($pathinfo, $localeName); if ($urlDetails) { + foreach ($this->storageRouterEnhancerPlugins as $storageRouterEnhancerPlugin) { + $urlDetails = $storageRouterEnhancerPlugin->afterMatch($urlDetails, $requestContext); + } + $urlDetails[static::ATTRIBUTE_PATH_INFO] = $pathinfo; + return $urlDetails; } } diff --git a/src/SprykerShop/Yves/StorageRouter/RouteEnhancer/ControllerRouteEnhancer.php b/src/SprykerShop/Yves/StorageRouter/RouteEnhancer/ControllerRouteEnhancer.php index d32f7c7..0a25507 100644 --- a/src/SprykerShop/Yves/StorageRouter/RouteEnhancer/ControllerRouteEnhancer.php +++ b/src/SprykerShop/Yves/StorageRouter/RouteEnhancer/ControllerRouteEnhancer.php @@ -16,6 +16,16 @@ class ControllerRouteEnhancer implements RouteEnhancerInterface { + /** + * @var string + */ + protected const ATTRIBUTE_PATH_INFO = 'pathinfo'; + + /** + * @var string + */ + protected const ATTRIBUTE_DATA = 'data'; + /** * @var array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\ResourceCreatorPluginInterface> */ @@ -39,7 +49,10 @@ public function enhance(array $defaults, Request $request): array { foreach ($this->resourceCreatorPlugins as $resourceCreator) { if ($defaults['type'] === $resourceCreator->getType()) { - return $this->createResource($resourceCreator, $defaults['data']); + $resource = $this->createResource($resourceCreator, $defaults[static::ATTRIBUTE_DATA]); + $resource[static::ATTRIBUTE_PATH_INFO] = $defaults[static::ATTRIBUTE_PATH_INFO]; + + return $resource; } } diff --git a/src/SprykerShop/Yves/StorageRouter/StorageRouterConfig.php b/src/SprykerShop/Yves/StorageRouter/StorageRouterConfig.php index 2c40aba..80586ad 100644 --- a/src/SprykerShop/Yves/StorageRouter/StorageRouterConfig.php +++ b/src/SprykerShop/Yves/StorageRouter/StorageRouterConfig.php @@ -8,7 +8,38 @@ namespace SprykerShop\Yves\StorageRouter; use Spryker\Yves\Kernel\AbstractBundleConfig; +use SprykerShop\Shared\StorageRouter\StorageRouterConstants; class StorageRouterConfig extends AbstractBundleConfig { + /** + * Specification: + * - Returns a list of supported stores for Route manipulation. + * - Will be used to strip of store information from a route before a route is matched. + * + * @api + * + * @example Incoming URL `/DE/home` will be manipulated to `/home` because the router only knows URL's without any optional pre/suffix. + * + * @see \Spryker\Yves\Router\Plugin\RouterEnhancer\StorePrefixRouterEnhancerPlugin + * + * @return array + */ + public function getAllowedStores(): array + { + return []; + } + + /** + * Specification: + * - Returns true if the store routing is enabled. + * + * @api + * + * @return bool + */ + public function isStoreRoutingEnabled(): bool + { + return $this->get(StorageRouterConstants::IS_STORE_ROUTING_ENABLED, false); + } } diff --git a/src/SprykerShop/Yves/StorageRouter/StorageRouterDependencyProvider.php b/src/SprykerShop/Yves/StorageRouter/StorageRouterDependencyProvider.php index d5ecffe..b1ca0c7 100644 --- a/src/SprykerShop/Yves/StorageRouter/StorageRouterDependencyProvider.php +++ b/src/SprykerShop/Yves/StorageRouter/StorageRouterDependencyProvider.php @@ -10,6 +10,7 @@ use Spryker\Yves\Kernel\AbstractBundleDependencyProvider; use Spryker\Yves\Kernel\Container; use SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToUrlStorageClientBridge; +use SprykerShop\Yves\StorageRouter\Dependency\Client\StorageStorageRouterToStoreClientBridge; /** * @method \SprykerShop\Yves\StorageRouter\StorageRouterConfig getConfig() @@ -26,6 +27,16 @@ class StorageRouterDependencyProvider extends AbstractBundleDependencyProvider */ public const PLUGIN_RESOURCE_CREATORS = 'PLUGIN_RESOURCE_CREATORS'; + /** + * @var string + */ + public const PLUGINS_ROUTER_ENHANCER = 'PLUGINS_ROUTER_ENHANCER'; + + /** + * @var string + */ + public const CLIENT_STORE = 'CLIENT_STORE'; + /** * @param \Spryker\Yves\Kernel\Container $container * @@ -35,6 +46,8 @@ public function provideDependencies(Container $container) { $container = $this->addUrlStorageClient($container); $container = $this->addResourceCreatorPlugins($container); + $container = $this->addStorageRouterEnhancerPlugins($container); + $container = $this->addStoreClient($container); return $container; } @@ -67,6 +80,20 @@ protected function addResourceCreatorPlugins(Container $container): Container return $container; } + /** + * @param \Spryker\Yves\Kernel\Container $container + * + * @return \Spryker\Yves\Kernel\Container + */ + protected function addStorageRouterEnhancerPlugins(Container $container): Container + { + $container->set(static::PLUGINS_ROUTER_ENHANCER, function () { + return $this->getStorageRouterEnhancerPlugins(); + }); + + return $container; + } + /** * @return array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\ResourceCreatorPluginInterface> */ @@ -74,4 +101,26 @@ protected function getResourceCreatorPlugins() { return []; } + + /** + * @return array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> + */ + protected function getStorageRouterEnhancerPlugins(): array + { + return []; + } + + /** + * @param \Spryker\Yves\Kernel\Container $container + * + * @return \Spryker\Yves\Kernel\Container + */ + protected function addStoreClient(Container $container): Container + { + $container->set(static::CLIENT_STORE, function (Container $container) { + return new StorageStorageRouterToStoreClientBridge($container->getLocator()->store()->client()); + }); + + return $container; + } } diff --git a/src/SprykerShop/Yves/StorageRouter/StorageRouterFactory.php b/src/SprykerShop/Yves/StorageRouter/StorageRouterFactory.php index 5d00cbe..8a2cced 100644 --- a/src/SprykerShop/Yves/StorageRouter/StorageRouterFactory.php +++ b/src/SprykerShop/Yves/StorageRouter/StorageRouterFactory.php @@ -8,6 +8,7 @@ namespace SprykerShop\Yves\StorageRouter; use Spryker\Yves\Kernel\AbstractFactory; +use SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToStoreClientInterface; use SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToUrlStorageClientInterface; use SprykerShop\Yves\StorageRouter\ParameterMerger\ParameterMerger; use SprykerShop\Yves\StorageRouter\ParameterMerger\ParameterMergerInterface; @@ -53,6 +54,7 @@ public function createRequestMatcher(): RequestMatcherInterface { return new StorageRequestMatcher( $this->getUrlStorageClient(), + $this->getStorageRouterEnhancerPlugins(), ); } @@ -64,6 +66,7 @@ public function createUrlGenerator(): UrlGeneratorInterface return new StorageUrlGenerator( $this->getUrlStorageClient(), $this->createParameterMerger(), + $this->getStorageRouterEnhancerPlugins(), ); } @@ -90,4 +93,20 @@ public function createParameterMerger(): ParameterMergerInterface { return new ParameterMerger(); } + + /** + * @return array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> + */ + public function getStorageRouterEnhancerPlugins(): array + { + return $this->getProvidedDependency(StorageRouterDependencyProvider::PLUGINS_ROUTER_ENHANCER); + } + + /** + * @return \SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToStoreClientInterface + */ + public function getStoreClient(): StorageRouterToStoreClientInterface + { + return $this->getProvidedDependency(StorageRouterDependencyProvider::CLIENT_STORE); + } } diff --git a/src/SprykerShop/Yves/StorageRouter/UrlGenerator/StorageUrlGenerator.php b/src/SprykerShop/Yves/StorageRouter/UrlGenerator/StorageUrlGenerator.php index fed2c4f..878db12 100644 --- a/src/SprykerShop/Yves/StorageRouter/UrlGenerator/StorageUrlGenerator.php +++ b/src/SprykerShop/Yves/StorageRouter/UrlGenerator/StorageUrlGenerator.php @@ -31,14 +31,24 @@ class StorageUrlGenerator implements UrlGeneratorInterface */ protected $parameterMerger; + /** + * @var array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> + */ + protected array $storageRouterEnhancerPlugins; + /** * @param \SprykerShop\Yves\StorageRouter\Dependency\Client\StorageRouterToUrlStorageClientInterface $urlStorageClient * @param \SprykerShop\Yves\StorageRouter\ParameterMerger\ParameterMergerInterface $parameterMerger + * @param array<\SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface> $storageRouterEnhancerPlugins */ - public function __construct(StorageRouterToUrlStorageClientInterface $urlStorageClient, ParameterMergerInterface $parameterMerger) - { + public function __construct( + StorageRouterToUrlStorageClientInterface $urlStorageClient, + ParameterMergerInterface $parameterMerger, + array $storageRouterEnhancerPlugins + ) { $this->urlStorageClient = $urlStorageClient; $this->parameterMerger = $parameterMerger; + $this->storageRouterEnhancerPlugins = $storageRouterEnhancerPlugins; } /** @@ -71,10 +81,17 @@ public function getContext(): RequestContext public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string { $localeName = $this->getContext()->getParameter('_locale'); + foreach ($this->storageRouterEnhancerPlugins as $storageRouterEnhancerPlugin) { + $name = $storageRouterEnhancerPlugin->beforeMatch($name, $this->getContext()); + } if (!$this->urlStorageClient->matchUrl($name, $localeName)) { throw new RouteNotFoundException(); } + foreach ($this->storageRouterEnhancerPlugins as $storageRouterEnhancerPlugin) { + $storageRouterEnhancerPlugin->afterMatch([], $this->getContext()); + } + parse_str($this->getContext()->getQueryString(), $requestParameter); $queryString = http_build_query($this->parameterMerger->mergeParameters($requestParameter, $parameters)); @@ -83,7 +100,13 @@ public function generate(string $name, array $parameters = [], int $referenceTyp $name .= '?' . $queryString; } - return $this->getUrlOrPathForType($name, $referenceType); + $url = $this->getUrlOrPathForType($name, $referenceType); + + foreach (array_reverse($this->storageRouterEnhancerPlugins) as $storageRouterEnhancerPlugin) { + $url = $storageRouterEnhancerPlugin->afterGenerate($url, $this->getContext(), $referenceType); + } + + return $url; } /**