Skip to content

Commit 1b07253

Browse files
committed
add expression field
1 parent d654fe3 commit 1b07253

27 files changed

+674
-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/field_types.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,80 @@ $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+
<details open><summary>Yaml</summary>
337+
338+
```yaml
339+
# config/packages/sylius_grid.yaml
340+
341+
sylius_grid:
342+
grids:
343+
app_user:
344+
fields:
345+
price:
346+
type: expression
347+
label: app.ui.price
348+
options:
349+
expression: 'value ~ "$"'
350+
351+
role:
352+
type: expression
353+
label: app.ui.price
354+
options:
355+
expression: '"<strong>" ~ value ~ "</strong>"'
356+
htmlspecialchars: false
357+
358+
most_exensive_order_total:
359+
type: expression
360+
label: app.ui.most_exensive_order_total
361+
options:
362+
expression: 'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()'
363+
```
364+
365+
</details>
366+
367+
<details open><summary>PHP</summary>
368+
369+
```php
370+
<?php
371+
// config/packages/sylius_grid.php
372+
373+
use Sylius\Bundle\GridBundle\Builder\Field\ExpressionField;
374+
use Sylius\Bundle\GridBundle\Builder\GridBuilder;
375+
use Sylius\Bundle\GridBundle\Config\GridConfig;
376+
377+
return static function (GridConfig $grid): void {
378+
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
379+
->addField(
380+
ExpressionField::create('price', 'value ~ "$"')
381+
->setLabel('app.ui.price')
382+
)
383+
->addField(
384+
ExpressionField::create('role', '"<strong>" ~ value ~ "</strong>"', htmlspecialchars: false)
385+
->setLabel('app.ui.role')
386+
)
387+
->addField(
388+
ExpressionField::create(
389+
'most_expensive_order_total',
390+
'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()',
391+
)
392+
->setLabel('app.ui.most_exensive_order_total')
393+
)
394+
);
395+
};
396+
```
397+
398+
</details>
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
}

src/Bundle/Tests/Provider/ServiceGridProviderTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function test_grids_inheritance(): void
3535
$this->assertEquals([
3636
'title',
3737
'author',
38+
'price',
3839
'id',
3940
], array_keys($gridDefinition->getFields()));
4041

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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\Component\Grid\Attribute;
15+
16+
#[\Attribute(\Attribute::TARGET_CLASS)]
17+
final class AsExpressionProvider
18+
{
19+
public const SERVICE_TAG = 'sylius.grid.provider';
20+
}

0 commit comments

Comments
 (0)