Skip to content

Commit

Permalink
add expression field
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian-Merle committed Dec 11, 2024
1 parent e2902ed commit eed11b4
Show file tree
Hide file tree
Showing 28 changed files with 676 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ jobs:
composer analyse
(cd src/Component && composer validate --strict)
-
name: Run spec tests
run: vendor/bin/phpspec run

-
name: Run component tests
run: (cd src/Component && vendor/bin/phpspec run)
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0",
"symfony/deprecation-contracts": "^2.2 || ^3.1",
"symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0",
"symfony/expression-language": "^5.4 || ^6.4 || ^7.0",
"symfony/form": "^5.4 || ^6.4 || ^7.0",
"symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0",
"symfony/http-kernel": "^5.4 || ^6.4 || ^7.0",
Expand Down Expand Up @@ -97,11 +98,11 @@
},
"autoload-dev": {
"psr-4": {
"Sylius\\Bundle\\GridBundle\\spec\\": "src/Bundle/spec/",
"Sylius\\Component\\Grid\\spec\\": "src/Component/spec/",
"App\\": "tests/Application/src/",
"AppBundle\\": "src/Bundle/test/src/AppBundle/",
"App\\Tests\\Tmp\\": "tests/Application/tmp/"
"App\\Tests\\Tmp\\": "tests/Application/tmp/",
"spec\\Sylius\\Bundle\\GridBundle\\": "src/Bundle/spec/",
"spec\\Sylius\\Component\\Grid\\": "src/Component/spec/"
}
},
"config": {
Expand All @@ -121,7 +122,6 @@
"vendor/bin/ecs check --fix"
],
"test-yaml-config": [
"vendor/bin/phpspec run --ansi --no-interaction",
"APP_ENV=test_grids_with_yaml_config vendor/bin/phpunit --colors=always"
],
"test-php-config": [
Expand Down
75 changes: 75 additions & 0 deletions docs/field_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,78 @@ $field->setOptions([
// Your options here
]);
```

Expression
----------

The **Expression** field type provides flexibility by allowing you to specify an expression that will be evaluated on the fly.
This feature uses Symfony's expression language, making it versatile and powerful without needing to create additional callbacks or templates.

The expression will be evaluated with the following context:

- `value`: The value of the row.

You can also control whether special characters in the output are escaped by setting the `htmlspecialchars` option.
By default, this is enabled, but you can disable it if the output contains HTML elements that should render as HTML.

<details open><summary>Yaml</summary>

```yaml
# config/packages/sylius_grid.yaml

sylius_grid:
grids:
app_user:
fields:
price:
type: expression
label: app.ui.price
options:
expression: 'value ~ "$"'
role:
type: expression
label: app.ui.price
options:
expression: '"<strong>" ~ value ~ "</strong>"'
htmlspecialchars: false
most_exensive_order_total:
type: expression
label: app.ui.most_exensive_order_total
options:
expression: 'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()'
```
</details>
<details open><summary>PHP</summary>
```php
<?php
// config/packages/sylius_grid.php

use Sylius\Bundle\GridBundle\Builder\Field\ExpressionField;
use Sylius\Bundle\GridBundle\Builder\GridBuilder;
use Sylius\Bundle\GridBundle\Config\GridConfig;

return static function (GridConfig $grid): void {
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
->addField(
ExpressionField::create('price', 'value ~ "$"')
->setLabel('app.ui.price')
)
->addField(
ExpressionField::create('role', '"<strong>" ~ value ~ "</strong>"', htmlspecialchars: false)
->setLabel('app.ui.role')
)
->addField(
ExpressionField::create(
'most_expensive_order_total',
'container.get("sylius.repository.order").findMostExpensiveOrder(value).getTotal()',
)
->setLabel('app.ui.most_exensive_order_total')
)
);
};
```

</details>
28 changes: 28 additions & 0 deletions src/Bundle/Builder/Field/ExpressionField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\GridBundle\Builder\Field;

final class ExpressionField
{
public static function create(
string $name,
string $expression,
bool $htmlspecialchars = true,
): FieldInterface {
return Field::create($name, 'expression')
->setOption('expression', $expression)
->setOption('htmlspecialchars', $htmlspecialchars)
;
}
}
17 changes: 17 additions & 0 deletions src/Bundle/DependencyInjection/SyliusGridExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
use Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle;
use Sylius\Bundle\GridBundle\Grid\GridInterface;
use Sylius\Bundle\GridBundle\SyliusGridBundle;
use Sylius\Component\Grid\Attribute\AsExpressionProvider;
use Sylius\Component\Grid\Attribute\AsExpressionVariables;
use Sylius\Component\Grid\Data\DataProviderInterface;
use Sylius\Component\Grid\Filtering\FilterInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
Expand Down Expand Up @@ -67,6 +70,20 @@ public function load(array $configs, ContainerBuilder $container): void
$container->registerForAutoconfiguration(DataProviderInterface::class)
->addTag('sylius.grid_data_provider')
;

$container->registerAttributeForAutoconfiguration(
AsExpressionVariables::class,
static function (ChildDefinition $definition, AsExpressionVariables $attribute, \Reflector $reflector): void {
$definition->addTag(AsExpressionVariables::SERVICE_TAG);
},
);

$container->registerAttributeForAutoconfiguration(
AsExpressionProvider::class,
static function (ChildDefinition $definition, AsExpressionProvider $attribute, \Reflector $reflector): void {
$definition->addTag(AsExpressionProvider::SERVICE_TAG);
},
);
}

public function getConfiguration(array $config, ContainerBuilder $container): Configuration
Expand Down
1 change: 1 addition & 0 deletions src/Bundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<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">
<imports>
<import resource="services/expression_language.xml" />
<import resource="services/field_types.xml" />
<import resource="services/filters.xml" />
<import resource="services/templating.xml" />
Expand Down
32 changes: 32 additions & 0 deletions src/Bundle/Resources/config/services/expression_language.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
This file is part of the Sylius package.
(c) Sylius Sp. z o.o.
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->

<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">
<services>
<service id="sylius_grid.expression_language.factory" class="Sylius\Component\Grid\ExpressionLanguage\ExpressionLanguageFactory">
<argument type="tagged_iterator" tag="sylius.grid.provider" />
</service>
<service id="Sylius\Component\Grid\ExpressionLanguage\ExpressionLanguageFactory" alias="sylius_grid.expression_language.factory" />

<service id="sylius_grid.expression_language" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage">
<factory service="sylius_grid.expression_language.factory" />
</service>

<service id="sylius_grid.expression_language.expression_evaluator" class="Sylius\Component\Grid\ExpressionLanguage\ExpressionEvaluator">
<argument type="service" id="sylius_grid.expression_language" />
<argument type="service" id="sylius_grid.expression_language.variables_collection_aggregate" />
</service>
<service id="Sylius\Component\Grid\ExpressionLanguage\ExpressionEvaluator" alias="sylius_grid.expression_language.expression_evaluator" />

<service id="sylius_grid.expression_language.variables_collection_aggregate" class="Sylius\Component\Grid\ExpressionLanguage\VariablesCollectionAggregate">
<argument type="tagged_iterator" tag="sylius.grid.variables" />
</service>
<service id="Sylius\Component\Grid\ExpressionLanguage\VariablesCollectionAggregate" alias="sylius_grid.expression_language.variables_collection_aggregate" />
</services>
</container>
7 changes: 7 additions & 0 deletions src/Bundle/Resources/config/services/field_types.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
</service>
<service id="Sylius\Component\Grid\FieldTypes\DatetimeFieldType" alias="sylius.grid_field.datetime" />

<service id="sylius.grid_field.expression" class="Sylius\Component\Grid\FieldTypes\ExpressionFieldType">
<argument type="service" id="sylius.grid.data_extractor" />
<argument type="service" id="sylius_grid.expression_language.expression_evaluator" />
<tag name="sylius.grid_field" type="expression" />
</service>
<service id="Sylius\Component\Grid\FieldTypes\ExpressionFieldType" alias="sylius.grid_field.expression" />

<service id="sylius.grid_field.string" class="Sylius\Component\Grid\FieldTypes\StringFieldType">
<argument type="service" id="sylius.grid.data_extractor" />
<tag name="sylius.grid_field" type="string" />
Expand Down
33 changes: 33 additions & 0 deletions src/Bundle/Tests/Functional/GridUiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ public function it_filters_books_by_title(): void
$this->assertSame('Book 5', $titles[0]);
}

/** @test */
public function it_shows_books_prices(): void
{
$this->client->request('GET', '/books/');

$prices = $this->getBookPriceFromResponse();

$this->assertListContainsOnly($prices, [
'42 €',
'10 £',
]);
}

/** @test */
public function it_filters_books_by_title_with_contains(): void
{
Expand Down Expand Up @@ -274,6 +287,16 @@ private function getBookAuthorNationalitiesFromResponse(): array
);
}

/** @return string[] */
private function getBookPriceFromResponse(): array
{
return $this->getCrawler()
->filter('[data-test-price]')
->each(
fn (Crawler $node): string => $node->text(),
);
}

/** @return string[] */
private function getAuthorNamesFromResponse(): array
{
Expand All @@ -293,4 +316,14 @@ protected function buildMatcher(): Matcher
{
return $this->matcherFactory->createMatcher(new VoidBacktrace());
}

private function assertListContainsOnly(array $list, array $allowedValues)
{
foreach ($list as $item) {
$this->assertTrue(
in_array($item, $allowedValues, true),
"Item '$item' is not in the allowed list.",
);
}
}
}
1 change: 1 addition & 0 deletions src/Bundle/Tests/Provider/ServiceGridProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function test_grids_inheritance(): void
$this->assertEquals([
'title',
'author',
'price',
'id',
], array_keys($gridDefinition->getFields()));

Expand Down
20 changes: 20 additions & 0 deletions src/Component/Attribute/AsExpressionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Component\Grid\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS)]
final class AsExpressionProvider
{
public const SERVICE_TAG = 'sylius.grid.provider';
}
20 changes: 20 additions & 0 deletions src/Component/Attribute/AsExpressionVariables.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Component\Grid\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS)]
final class AsExpressionVariables
{
public const SERVICE_TAG = 'sylius.grid.variables';
}
39 changes: 39 additions & 0 deletions src/Component/ExpressionLanguage/ExpressionEvaluator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Component\Grid\ExpressionLanguage;

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

/**
* @experimental
*/
final class ExpressionEvaluator implements ExpressionEvaluatorInterface
{
public function __construct(
private ExpressionLanguage $expressionLanguage,
private VariablesCollectionInterface $variablesCollection,
) {
}

public function evaluateExpression(string $expression, array $variables = []): mixed
{
return $this->expressionLanguage->evaluate(
$expression,
array_merge(
$this->variablesCollection->getCollection(),
$variables,
),
);
}
}
Loading

0 comments on commit eed11b4

Please sign in to comment.