Skip to content

Commit

Permalink
FRW-7936 Allow to provide store name as part of URL path. (#2529)
Browse files Browse the repository at this point in the history
FRW-7936 Allow to provide store name as part of URL path.
  • Loading branch information
dimitriyTsemma authored Nov 28, 2024
1 parent f8afe35 commit b1e57a2
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 8 deletions.
9 changes: 7 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
24 changes: 24 additions & 0 deletions src/SprykerShop/Shared/StorageRouter/StorageRouterConstants.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace SprykerShop\Shared\StorageRouter;

/**
* Declares global environment configuration keys. Do not use it for other class constants.
*/
interface StorageRouterConstants
{
/**
* Specification:
* - Returns true if the store routing is enabled.
*
* @api
*
* @var string
*/
public const IS_STORE_ROUTING_ENABLED = 'STORAGE_ROUTER:IS_STORE_ROUTING_ENABLED';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<transfers xmlns="spryker:transfer-01" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="spryker:transfer-01 http://static.spryker.com/transfer-01.xsd">

<transfer name="Store">
<property name="name" type="string"/>
</transfer>

</transfers>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace SprykerShop\Yves\StorageRouter\Dependency\Client;

use Generated\Shared\Transfer\StoreTransfer;

interface StorageRouterToStoreClientInterface
{
/**
* @return \Generated\Shared\Transfer\StoreTransfer
*/
public function getCurrentStore(): StoreTransfer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace SprykerShop\Yves\StorageRouter\Dependency\Client;

use Generated\Shared\Transfer\StoreTransfer;

class StorageStorageRouterToStoreClientBridge implements StorageRouterToStoreClientInterface
{
/**
* @var \Spryker\Client\Store\StoreClientInterface
*/
protected $storeClient;

/**
* @param \Spryker\Client\Store\StoreClientInterface $storeClient
*/
public function __construct($storeClient)
{
$this->storeClient = $storeClient;
}

/**
* @return \Generated\Shared\Transfer\StoreTransfer
*/
public function getCurrentStore(): StoreTransfer
{
return $this->storeClient->getCurrentStore();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace SprykerShop\Yves\StorageRouter\Plugin\RouterEnhancer;

use Spryker\Service\UtilText\Model\Url\Url;
use Spryker\Yves\Kernel\AbstractPlugin;
use SprykerShop\Yves\StorageRouter\Router\DynamicRouter;
use SprykerShop\Yves\StorageRouterExtension\Dependency\Plugin\StorageRouterEnhancerPluginInterface;
use Symfony\Component\Routing\RequestContext;

/**
* @method \SprykerShop\Yves\StorageRouter\StorageRouterConfig getConfig()
* @method \SprykerShop\Yves\StorageRouter\StorageRouterFactory getFactory()
*/
class StorePrefixStorageRouterEnhancerPlugin extends AbstractPlugin implements StorageRouterEnhancerPluginInterface
{
/**
* @var string
*/
protected const PARAMETER_STORE = 'store';

/**
* @var string|null
*/
protected $currentStore;

/**
* @param string $pathinfo
* @param \Symfony\Component\Routing\RequestContext $requestContext
*
* @return string
*/
public function beforeMatch(string $pathinfo, RequestContext $requestContext): string
{
if ($pathinfo === '/') {
return $pathinfo;
}

$pathinfoFragments = explode('/', trim($pathinfo, '/'));
if (in_array($pathinfoFragments[0], $this->getConfig()->getAllowedStores(), true)) {
$this->currentStore = array_shift($pathinfoFragments);

return '/' . implode('/', $pathinfoFragments);
}

return $pathinfo;
}

/**
* @param array<mixed> $parameters
* @param \Symfony\Component\Routing\RequestContext $requestContext
*
* @return array<mixed>
*/
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
*/
Expand All @@ -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;
}
}

Expand Down
31 changes: 31 additions & 0 deletions src/SprykerShop/Yves/StorageRouter/StorageRouterConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>
*/
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);
}
}
Loading

0 comments on commit b1e57a2

Please sign in to comment.