Skip to content

Commit 7589179

Browse files
committed
add expression field
1 parent d654fe3 commit 7589179

29 files changed

+714
-3
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0",
3636
"symfony/deprecation-contracts": "^2.2 || ^3.1",
3737
"symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0",
38+
"symfony/expression-language": "^5.4 || ^6.0 || ^7.0",
3839
"symfony/form": "^5.4 || ^6.4 || ^7.0",
3940
"symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0",
4041
"symfony/http-kernel": "^5.4 || ^6.4 || ^7.0",
@@ -97,8 +98,8 @@
9798
},
9899
"autoload-dev": {
99100
"psr-4": {
100-
"Sylius\\Bundle\\GridBundle\\spec\\": "src/Bundle/spec/",
101-
"Sylius\\Component\\Grid\\spec\\": "src/Component/spec/",
101+
"spec\\Sylius\\Bundle\\GridBundle\\": "src/Bundle/spec/",
102+
"spec\\Sylius\\Component\\Grid\\": "src/Component/spec/",
102103
"App\\": "tests/Application/src/",
103104
"AppBundle\\": "src/Bundle/test/src/AppBundle/",
104105
"App\\Tests\\Tmp\\": "tests/Application/tmp/"

docs/expression_language.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Expression Language
2+
===================
3+
4+
The Sylius Grid Bundle utilizes the Symfony `ExpressionLanguage` component to provide a flexible and powerful way to evaluate expressions.
5+
6+
Evaluating Expressions
7+
----------------------
8+
9+
Expressions can be evaluated using the `sylius_grid.expression_language.expression_evaluator` service, which is an instance of `ExpressionEvaluator`.
10+
11+
The `ExpressionEvaluator` uses both an instance of `ExpressionLanguage` and a `VariablesCollectionInterface` (`sylius_grid.expression_language.variables_collection_aggregate`). This ensures that the evaluator has access to a collection of variables that developers can add.
12+
13+
To evaluate an expression:
14+
- The `evaluateExpression` method is called with the expression string and an optional array of variables.
15+
- The evaluator merges the provided variables with the collection of variables provided by the `VariablesCollectionAggregate` mentioned earlier.
16+
- The expression is then evaluated with all the variables.
17+
18+
**Note that:**
19+
- The `ExpressionLanguage` can be extended using expression providers.
20+
- `sylius_grid.expression_language.variables_collection_aggregate` can be extended using custom variables collections.
21+
22+
Extending the Expression Language
23+
---------------------------------
24+
25+
### Adding Expression Providers
26+
27+
The Symfony `ExpressionLanguage` component can be extended with functions using expression providers. To create a custom expression provider:
28+
29+
1. Your class should implement `Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface`.
30+
2. Tag your service with `sylius.grid.provider`. You can automate this by using the `Sylius\Component\Grid\Attribute\AsExpressionProvider` attribute.
31+
32+
### Adding Custom Variables
33+
34+
Adding custom variables is done by creating a new variables collection:
35+
36+
1. Create a class that implements `Sylius\Component\Grid\ExpressionLanguage\VariablesCollectionInterface`.
37+
2. Tag your service with `sylius.grid.variables`. You can automate this by using the `Sylius\Component\Grid\Attribute\AsExpressionVariables` attribute.

docs/field_types.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,82 @@ $field->setOptions([
319319
// Your options here
320320
]);
321321
```
322+
323+
Expression
324+
----------
325+
326+
The **Expression** column provides flexibility by allowing you to specify an expression that will be evaluated on the fly.
327+
This feature uses Symfony's expression language, making it versatile and powerful without needing to create additional callbacks or templates.
328+
329+
The expression will be evaluated with the following context:
330+
331+
- `value`: The value of the row.
332+
333+
You can also control whether special characters in the output are escaped by setting the `htmlspecialchars` option.
334+
By default, this is enabled, but you can disable it if the output contains HTML elements that should render as HTML.
335+
336+
If you need to access specific variables or extend the functions available in the expression, see [expression language](expression_language.md).
337+
338+
<details open><summary>Yaml</summary>
339+
340+
```yaml
341+
# config/packages/sylius_grid.yaml
342+
343+
sylius_grid:
344+
grids:
345+
app_user:
346+
fields:
347+
price:
348+
type: expression
349+
label: app.ui.price
350+
options:
351+
expression: 'value ~ "$"'
352+
353+
role:
354+
type: expression
355+
label: app.ui.price
356+
options:
357+
expression: '"<strong>" ~ value ~ "</strong>"'
358+
htmlspecialchars: false
359+
360+
most_exensive_order_total:
361+
type: expression
362+
label: app.ui.most_exensive_order_total
363+
options:
364+
expression: 'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()'
365+
```
366+
367+
</details>
368+
369+
<details open><summary>PHP</summary>
370+
371+
```php
372+
<?php
373+
// config/packages/sylius_grid.php
374+
375+
use Sylius\Bundle\GridBundle\Builder\Field\ExpressionField;
376+
use Sylius\Bundle\GridBundle\Builder\GridBuilder;
377+
use Sylius\Bundle\GridBundle\Config\GridConfig;
378+
379+
return static function (GridConfig $grid): void {
380+
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
381+
->addField(
382+
ExpressionField::create('price', 'value ~ "$"')
383+
->setLabel('app.ui.price')
384+
)
385+
->addField(
386+
ExpressionField::create('role', '"<strong>" ~ value ~ "</strong>"', htmlspecialchars: false)
387+
->setLabel('app.ui.role')
388+
)
389+
->addField(
390+
ExpressionField::create(
391+
'most_expensive_order_total',
392+
'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()',
393+
)
394+
->setLabel('app.ui.most_exensive_order_total')
395+
)
396+
);
397+
};
398+
```
399+
400+
</details>

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Menu
2222
* [Creating your First Grid](your_first_grid.md)
2323
* [Configuring Fields](field_configuration.md)
2424
* [Field types](field_types.md)
25+
* [Expression language](expression_language.md)
2526
* [Creating custom Field type](custom_field_type.md)
2627
* [Creating custom Action](custom_action.md)
2728
* [Creating custom Bulk Action](custom_bulk_action.md)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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\Bundle\GridBundle\Builder\Field;
15+
16+
final class ExpressionField
17+
{
18+
public static function create(
19+
string $name,
20+
string $expression,
21+
bool $htmlspecialchars = true,
22+
): FieldInterface {
23+
return Field::create($name, 'expression')
24+
->setOption('expression', $expression)
25+
->setOption('htmlspecialchars', $htmlspecialchars)
26+
;
27+
}
28+
}

src/Bundle/DependencyInjection/SyliusGridExtension.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
use Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle;
1717
use Sylius\Bundle\GridBundle\Grid\GridInterface;
1818
use Sylius\Bundle\GridBundle\SyliusGridBundle;
19+
use Sylius\Component\Grid\Attribute\AsExpressionProvider;
20+
use Sylius\Component\Grid\Attribute\AsExpressionVariables;
1921
use Sylius\Component\Grid\Data\DataProviderInterface;
2022
use Sylius\Component\Grid\Filtering\FilterInterface;
2123
use Symfony\Component\Config\FileLocator;
24+
use Symfony\Component\DependencyInjection\ChildDefinition;
2225
use Symfony\Component\DependencyInjection\ContainerBuilder;
2326
use Symfony\Component\DependencyInjection\Extension\Extension;
2427
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@@ -67,6 +70,20 @@ public function load(array $configs, ContainerBuilder $container): void
6770
$container->registerForAutoconfiguration(DataProviderInterface::class)
6871
->addTag('sylius.grid_data_provider')
6972
;
73+
74+
$container->registerAttributeForAutoconfiguration(
75+
AsExpressionVariables::class,
76+
static function (ChildDefinition $definition, AsExpressionVariables $attribute, \Reflector $reflector): void {
77+
$definition->addTag(AsExpressionVariables::SERVICE_TAG);
78+
},
79+
);
80+
81+
$container->registerAttributeForAutoconfiguration(
82+
AsExpressionProvider::class,
83+
static function (ChildDefinition $definition, AsExpressionProvider $attribute, \Reflector $reflector): void {
84+
$definition->addTag(AsExpressionProvider::SERVICE_TAG);
85+
},
86+
);
7087
}
7188

7289
public function getConfiguration(array $config, ContainerBuilder $container): Configuration

src/Bundle/Resources/config/services.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
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
<imports>
16+
<import resource="services/expression_language.xml" />
1617
<import resource="services/field_types.xml" />
1718
<import resource="services/filters.xml" />
1819
<import resource="services/templating.xml" />
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
5+
This file is part of the Sylius package.
6+
7+
(c) Sylius Sp. z o.o.
8+
9+
For the full copyright and license information, please view the LICENSE
10+
file that was distributed with this source code.
11+
12+
-->
13+
14+
<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">
15+
<services>
16+
<service id="sylius_grid.expression_language.factory" class="Sylius\Component\Grid\ExpressionLanguage\ExpressionLanguageFactory">
17+
<argument type="tagged_iterator" tag="sylius.grid.provider" />
18+
</service>
19+
20+
<service id="sylius_grid.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage">
21+
<factory service="sylius_grid.expression_language.factory" />
22+
</service>
23+
24+
<service id="sylius_grid.expression_language.expression_evaluator" class="Sylius\Component\Grid\ExpressionLanguage\ExpressionEvaluator">
25+
<argument type="service" id="sylius_grid.expression_language" />
26+
<argument type="service" id="sylius_grid.expression_language.variables_collection_aggregate" />
27+
</service>
28+
29+
<service id="sylius_grid.expression_language.variables_collection_aggregate" class="Sylius\Component\Grid\ExpressionLanguage\VariablesCollectionAggregate">
30+
<argument type="tagged_iterator" tag="sylius.grid.variables" />
31+
</service>
32+
</services>
33+
</container>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
</service>
2323
<service id="sylius.grid_field.datetime" alias="Sylius\Component\Grid\FieldTypes\DatetimeFieldType" />
2424

25+
<service id="Sylius\Component\Grid\FieldTypes\ExpressionFieldType">
26+
<argument type="service" id="sylius.grid.data_extractor" />
27+
<argument type="service" id="sylius_grid.expression_language.expression_evaluator" />
28+
<tag name="sylius.grid_field" type="expression" />
29+
</service>
30+
<service id="sylius.grid_field.expression" alias="Sylius\Component\Grid\FieldTypes\ExpressionFieldType" />
31+
2532
<service id="Sylius\Component\Grid\FieldTypes\StringFieldType">
2633
<argument type="service" id="sylius.grid.data_extractor" />
2734
<tag name="sylius.grid_field" type="string" />

src/Bundle/Tests/Functional/GridUiTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,19 @@ public function it_filters_books_by_title(): void
101101
$this->assertSame('Book 5', $titles[0]);
102102
}
103103

104+
/** @test */
105+
public function it_shows_books_prices(): void
106+
{
107+
$this->client->request('GET', '/books/');
108+
109+
$prices = $this->getBookPriceFromResponse();
110+
111+
$this->assertListContainsOnly($prices, [
112+
'42 €',
113+
'10 £',
114+
]);
115+
}
116+
104117
/** @test */
105118
public function it_filters_books_by_title_with_contains(): void
106119
{
@@ -274,6 +287,16 @@ private function getBookAuthorNationalitiesFromResponse(): array
274287
);
275288
}
276289

290+
/** @return string[] */
291+
private function getBookPriceFromResponse(): array
292+
{
293+
return $this->getCrawler()
294+
->filter('[data-test-price]')
295+
->each(
296+
fn (Crawler $node): string => $node->text(),
297+
);
298+
}
299+
277300
/** @return string[] */
278301
private function getAuthorNamesFromResponse(): array
279302
{
@@ -293,4 +316,14 @@ protected function buildMatcher(): Matcher
293316
{
294317
return $this->matcherFactory->createMatcher(new VoidBacktrace());
295318
}
319+
320+
private function assertListContainsOnly(array $list, array $allowedValues)
321+
{
322+
foreach ($list as $item) {
323+
$this->assertTrue(
324+
in_array($item, $allowedValues, true),
325+
"Item '$item' is not in the allowed list.",
326+
);
327+
}
328+
}
296329
}

0 commit comments

Comments
 (0)