Skip to content

Commit 09c01b4

Browse files
Add argument parser for resource and operation vars (#1043)
| Q | A | --------------- | ----- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Related tickets | | License | MIT The feature we need with the new routing system **Previous implementation:** ```yaml sylius_admin_catalog_promotion_product_variant_index: path: /catalog-promotions/{id}/variants defaults: _sylius: vars: catalogPromotion: expr:service('sylius.repository.catalog_promotion').find($id) ``` **New implementation:** ```php new Index(vars: [ 'catalogPromotion' => "@=sylius_repositories.get('sylius.repository.catalog_promotion').find(request.attributes.get('id')", ]); ``` **Note** `@=` is the synthax for expression that is used on Symfony dependency injection and Twig Hooks Example on Sylius E-commerce admin panel The product is used on the breadcrumb. ![image](https://github.com/user-attachments/assets/6a42d4c4-3ea9-405e-a25a-b733c30df5f5)
2 parents d82dd44 + 8f21e28 commit 09c01b4

File tree

13 files changed

+260
-4
lines changed

13 files changed

+260
-4
lines changed

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
<PossiblyNullArgument>
196196
<errorLevel type="suppress">
197197
<file name="src/Bundle/Grid/Parser/OptionsParser.php" />
198+
<file name="src/Component/src/Metadata/Operation/HttpOperationInitiator.php" />
198199
</errorLevel>
199200
</PossiblyNullArgument>
200201

src/Bundle/Context/Initiator/LegacyRequestContextInitiator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Sylius\Resource\Context\Initiator\RequestContextInitiatorInterface;
2020
use Sylius\Resource\Context\Option\MetadataOption;
2121
use Sylius\Resource\Metadata\RegistryInterface;
22+
use Sylius\Resource\Symfony\ExpressionLanguage\VarsResolverInterface;
2223
use Symfony\Component\HttpFoundation\Request;
2324

2425
final class LegacyRequestContextInitiator implements RequestContextInitiatorInterface
@@ -27,7 +28,17 @@ public function __construct(
2728
private RegistryInterface $resourceRegistry,
2829
private RequestConfigurationFactoryInterface $requestConfigurationFactory,
2930
private RequestContextInitiatorInterface $decorated,
31+
private ?VarsResolverInterface $varsResolver = null,
3032
) {
33+
if (null === $varsResolver) {
34+
trigger_deprecation(
35+
'sylius/resource-bundle',
36+
'1.14',
37+
'Not passing an instance of "%s" as the fourth constructor argument for "%s" is deprecated and will not be supported in 2.0.',
38+
VarsResolverInterface::class,
39+
self::class,
40+
);
41+
}
3142
}
3243

3344
public function initializeContext(Request $request): Context
@@ -49,7 +60,19 @@ public function initializeContext(Request $request): Context
4960
}
5061

5162
$configuration = $this->requestConfigurationFactory->create($metadata, $request);
63+
$configurationVars = $this->resolveVars($configuration->getVars());
64+
65+
$configuration->getParameters()->set('vars', $configurationVars);
5266

5367
return $context->with(new MetadataOption($metadata), new RequestConfigurationOption($configuration));
5468
}
69+
70+
private function resolveVars(array $vars): array
71+
{
72+
if (null === $this->varsResolver) {
73+
return $vars;
74+
}
75+
76+
return $this->varsResolver->resolve($vars);
77+
}
5578
}

src/Bundle/Resources/config/services/context.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<argument type="service" id="sylius.resource_registry" />
2424
<argument type="service" id="sylius.resource_controller.request_configuration_factory" />
2525
<argument type="service" id=".inner" />
26+
<argument type="service" id="sylius.expression_language.vars_resolver.metadata" />
2627
</service>
2728
</services>
2829
</container>

src/Bundle/Resources/config/services/expression_language.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,36 @@
1313

1414
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
1515
<services>
16+
<service id="sylius.metadata.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" />
1617
<service id="sylius.resource_factory.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" />
1718
<service id="sylius.repository.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" />
1819
<service id="sylius.routing.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" />
1920

2021
<service id="sylius.expression_language.variables.token" class="Sylius\Resource\Symfony\ExpressionLanguage\TokenVariables">
2122
<argument type="service" id="security.token_storage" on-invalid="null" />
23+
<tag name="sylius.metadata_variables" />
2224
<tag name="sylius.resource_factory_variables" />
2325
<tag name="sylius.repository_variables" />
2426
</service>
2527

2628
<service id="sylius.expression_language.variables.request" class="Sylius\Resource\Symfony\ExpressionLanguage\RequestVariables">
2729
<argument type="service" id="request_stack" />
30+
<tag name="sylius.metadata_variables" />
2831
<tag name="sylius.resource_factory_variables" />
2932
<tag name="sylius.repository_variables" />
3033
<tag name="sylius.routing_variables" />
3134
</service>
3235

3336
<service id="sylius.expression_language.variables.sylius_repositories" class="Sylius\Resource\Symfony\ExpressionLanguage\SyliusRepositoriesVariables">
3437
<argument type="tagged_locator" tag="sylius.repository" />
38+
<tag name="sylius.metadata_variables" />
3539
<tag name="sylius.resource_factory_variables" />
3640
</service>
3741

42+
<service id="sylius.expression_language.variables_collection.metadata" class="Sylius\Resource\Symfony\ExpressionLanguage\VariablesCollection">
43+
<argument type="tagged_iterator" tag="sylius.metadata_variables" />
44+
</service>
45+
3846
<service id="sylius.expression_language.variables_collection.factory" class="Sylius\Resource\Symfony\ExpressionLanguage\VariablesCollection">
3947
<argument type="tagged_iterator" tag="sylius.resource_factory_variables" />
4048
</service>
@@ -48,9 +56,20 @@
4856
</service>
4957

5058
<service id="sylius.expression_language.providers.throw_not_found_on_null" class="Sylius\Resource\Symfony\ExpressionLanguage\Provider\ThrowNotFoundOnNullExpressionFunctionProvider">
59+
<tag name="sylius.metadata_providers" />
5160
<tag name="sylius.resource_factory_providers" />
5261
</service>
5362

63+
<service id="sylius.expression_language.vars_resolver.metadata" class="Sylius\Resource\Symfony\ExpressionLanguage\VarsResolver">
64+
<argument type="service" id="sylius.expression_language.argument_parser.metadata" />
65+
</service>
66+
67+
<service id="sylius.expression_language.argument_parser.metadata" class="Sylius\Resource\Symfony\ExpressionLanguage\ArgumentParser">
68+
<argument type="service" id="sylius.metadata.expression_language" />
69+
<argument type="service" id="sylius.expression_language.variables_collection.metadata" />
70+
<argument type="tagged_iterator" tag="sylius.metadata_providers" />
71+
</service>
72+
5473
<service id="sylius.expression_language.argument_parser.factory" class="Sylius\Resource\Symfony\ExpressionLanguage\ArgumentParser">
5574
<argument type="service" id="sylius.resource_factory.expression_language" />
5675
<argument type="service" id="sylius.expression_language.variables_collection.factory" />

src/Bundle/Resources/config/services/metadata/resource_metadata_operation.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<service id="sylius.resource_metadata_operation.initiator.http_operation" class="Sylius\Resource\Metadata\Operation\HttpOperationInitiator">
1717
<argument type="service" id="sylius.resource_registry" />
1818
<argument type="service" id="sylius.resource_metadata_collection.factory" />
19+
<argument type="service" id="sylius.expression_language.vars_resolver.metadata" />
1920
</service>
2021
<service id="Sylius\Resource\Metadata\Operation\HttpOperationInitiatorInterface" alias="sylius.resource_metadata_operation.initiator.http_operation" />
2122
</services>

src/Bundle/spec/Context/Initiator/LegacyRequestContextInitiatorSpec.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ function it_adds_metadata_and_request_configuration_to_the_context(
4949
MetadataInterface $metadata,
5050
RequestConfiguration $requestConfiguration,
5151
): void {
52-
$request->attributes = new ParameterBag(['_sylius' => ['resource' => 'app.dummy']]);
52+
$parameterBag = new ParameterBag(['_sylius' => ['resource' => 'app.dummy']]);
53+
$requestConfiguration->getParameters()->willReturn($parameterBag);
54+
$requestConfiguration->getVars()->willReturn([]);
55+
$request->attributes = $parameterBag;
5356

5457
$decorated->initializeContext($request)->willReturn(new Context());
5558

src/Component/src/Metadata/Operation/HttpOperationInitiator.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@
1616
use Sylius\Resource\Metadata\HttpOperation;
1717
use Sylius\Resource\Metadata\RegistryInterface;
1818
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
19+
use Sylius\Resource\Symfony\ExpressionLanguage\VarsResolverInterface;
1920
use Symfony\Component\HttpFoundation\Request;
2021

2122
final class HttpOperationInitiator implements HttpOperationInitiatorInterface
2223
{
2324
public function __construct(
2425
private RegistryInterface $resourceRegistry,
2526
private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
27+
private ?VarsResolverInterface $varsResolver = null,
2628
) {
29+
if (null === $varsResolver) {
30+
trigger_deprecation(
31+
'sylius/resource-bundle',
32+
'1.14',
33+
'Not passing an instance of "%s" as the third constructor argument for "%s" is deprecated and will not be supported in 2.0.',
34+
VarsResolverInterface::class,
35+
self::class,
36+
);
37+
}
2738
}
2839

2940
public function initializeOperation(Request $request): ?HttpOperation
@@ -54,6 +65,32 @@ public function initializeOperation(Request $request): ?HttpOperation
5465
->getOperation($metadata->getAlias(), $operationName)
5566
;
5667

57-
return $operation;
68+
return $this->getOperationWithVars($operation);
69+
}
70+
71+
private function getOperationWithVars(HttpOperation $operation): HttpOperation
72+
{
73+
$operationVars = $operation->getVars();
74+
$resolvedOperationVars = $operationVars !== null ? $this->resolveVars($operationVars) : null;
75+
76+
$resourceVars = $operation->getResource()?->getVars();
77+
$resolvedResourceVars = $resourceVars !== null ? $this->resolveVars($resourceVars) : null;
78+
79+
if (null === $resolvedOperationVars && null === $resolvedResourceVars) {
80+
return $operation;
81+
}
82+
83+
$mergedVars = array_merge($resolvedResourceVars ?? [], $resolvedOperationVars ?? []);
84+
85+
return $operation->withVars($mergedVars);
86+
}
87+
88+
private function resolveVars(array $vars): array
89+
{
90+
if (null === $this->varsResolver) {
91+
return $vars;
92+
}
93+
94+
return $this->varsResolver->resolve($vars);
5895
}
5996
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Symfony\ExpressionLanguage;
15+
16+
/**
17+
* @experimental
18+
*/
19+
final class VarsResolver implements VarsResolverInterface
20+
{
21+
public function __construct(
22+
private readonly ArgumentParser $argumentParser,
23+
) {
24+
}
25+
26+
public function resolve(array $vars): array
27+
{
28+
foreach ($vars as $key => $value) {
29+
if (\is_array($value)) {
30+
$vars[$key] = $this->resolve($value);
31+
32+
continue;
33+
}
34+
35+
// Parse only vars that contain expressions
36+
if (!str_starts_with($value, '@=')) {
37+
continue;
38+
}
39+
40+
$value = str_replace('@=', '', $value);
41+
42+
$vars[$key] = $this->argumentParser->parseExpression($value);
43+
}
44+
45+
return $vars;
46+
}
47+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Sylius\Resource\Symfony\ExpressionLanguage;
15+
16+
/**
17+
* @experimental
18+
*/
19+
interface VarsResolverInterface
20+
{
21+
public function resolve(array $vars): array;
22+
}

src/Component/tests/Metadata/Operation/HttpOperationInitiatorTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
use Prophecy\PhpUnit\ProphecyTrait;
1818
use Prophecy\Prophecy\ObjectProphecy;
1919
use Sylius\Resource\Metadata\HttpOperation;
20+
use Sylius\Resource\Metadata\Index;
2021
use Sylius\Resource\Metadata\MetadataInterface;
2122
use Sylius\Resource\Metadata\Operation\HttpOperationInitiator;
2223
use Sylius\Resource\Metadata\Operations;
2324
use Sylius\Resource\Metadata\RegistryInterface;
2425
use Sylius\Resource\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2526
use Sylius\Resource\Metadata\Resource\ResourceMetadataCollection;
2627
use Sylius\Resource\Metadata\ResourceMetadata;
28+
use Sylius\Resource\Symfony\ExpressionLanguage\VarsResolverInterface;
2729
use Symfony\Component\HttpFoundation\ParameterBag;
2830
use Symfony\Component\HttpFoundation\Request;
2931

@@ -35,17 +37,21 @@ final class HttpOperationInitiatorTest extends TestCase
3537

3638
private ResourceMetadataCollectionFactoryInterface|ObjectProphecy $resourceMetadataCollectionFactory;
3739

40+
private VarsResolverInterface|ObjectProphecy $varsResolver;
41+
3842
protected function setUp(): void
3943
{
4044
$this->resourceRegistry = $this->prophesize(RegistryInterface::class);
4145
$this->resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
46+
$this->varsResolver = $this->prophesize(VarsResolverInterface::class);
4247
}
4348

4449
public function testItIsInitializable(): void
4550
{
4651
$initiator = new HttpOperationInitiator(
4752
$this->resourceRegistry->reveal(),
4853
$this->resourceMetadataCollectionFactory->reveal(),
54+
$this->varsResolver->reveal(),
4955
);
5056

5157
$this->assertInstanceOf(HttpOperationInitiator::class, $initiator);
@@ -72,6 +78,8 @@ public function testItInitializesHttpOperationsFromRequest(): void
7278
$metadata->getAlias()->willReturn('app.dummy');
7379

7480
$operation->getName()->willReturn('app_dummy_index');
81+
$operation->getVars()->willReturn(null);
82+
$operation->getResource()->willReturn(null);
7583

7684
$operations = new Operations();
7785
$operations->add('app_dummy_index', $operation->reveal());
@@ -89,6 +97,48 @@ public function testItInitializesHttpOperationsFromRequest(): void
8997
$this->assertSame($operation->reveal(), $initiator->initializeOperation($request->reveal()));
9098
}
9199

100+
public function testItResolvesOperationVars(): void
101+
{
102+
$request = $this->prophesize(Request::class);
103+
$attributes = $this->prophesize(ParameterBag::class);
104+
$metadata = $this->prophesize(MetadataInterface::class);
105+
$operation = new Index(name: 'app_dummy_index', vars: ['product' => '@=get_current_product()']);
106+
107+
$request->attributes = $attributes;
108+
109+
$attributes->get('_route')->willReturn('app_dummy_index');
110+
$attributes->all('_sylius')->willReturn([
111+
'resource' => 'app.dummy',
112+
]);
113+
$attributes->set('_sylius', ['resource' => 'app.dummy', 'resource_class' => 'App\DummyResource'])->shouldBeCalled();
114+
115+
$this->resourceRegistry->get('app.dummy')->willReturn($metadata);
116+
117+
$metadata->getClass('model')->willReturn('App\DummyResource');
118+
$metadata->getAlias()->willReturn('app.dummy');
119+
120+
$operations = new Operations();
121+
$operations->add('app_dummy_index', $operation);
122+
123+
$product = new \stdClass();
124+
$this->varsResolver->resolve(['product' => '@=get_current_product()'])->willReturn(['product' => $product])->shouldBeCalled();
125+
126+
$resourceMetadataCollection = new ResourceMetadataCollection();
127+
$resourceMetadataCollection[] = (new ResourceMetadata(alias: 'app.dummy'))->withOperations($operations);
128+
129+
$this->resourceMetadataCollectionFactory->create('App\DummyResource')->willReturn($resourceMetadataCollection);
130+
131+
$initiator = new HttpOperationInitiator(
132+
$this->resourceRegistry->reveal(),
133+
$this->resourceMetadataCollectionFactory->reveal(),
134+
$this->varsResolver->reveal(),
135+
);
136+
137+
$result = $initiator->initializeOperation($request->reveal());
138+
$this->assertNotNull($result);
139+
$this->assertSame(['product' => $product], $result->getVars());
140+
}
141+
92142
public function testItReturnsNullWhenRequestHasNoSyliusOptions(): void
93143
{
94144
$request = $this->prophesize(Request::class);

0 commit comments

Comments
 (0)