Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expression field #352

Open
wants to merge 1 commit into
base: 1.14
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line break is present in all YAML/PHP examples in this file. Should I remove it?

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
loic425 marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading