Skip to content

Commit

Permalink
make callback field compatible with yaml config
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian-Merle committed Jan 21, 2025
1 parent 7f9689b commit bf89482
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 35 deletions.
63 changes: 46 additions & 17 deletions docs/field_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,34 @@ Callback
The Callback column aims to offer almost as much flexibility as the Twig column, but without requiring the creation of a template.
You simply need to specify a callback, which allows you to transform the 'data' variable on the fly.

By default it uses the name of the field, but you can specify the path
alternatively. For example:
When defining callbacks in YAML, only string representations of callables are supported.
When configuring grids using PHP (as opposed to service grid configuration), both string and array callables are supported. However, closures cannot be used due to restrictions in Symfony's configuration (values of type "Closure" are not permitted in service configuration files).
By contrast, when configuring grids with service definitions, you can use both callables and closures.

Here are some examples of what you can do:

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

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

sylius_grid:
grids:
app_user:
fields:
id:
type: callback
options:
callback: "callback:App\\Helper\\GridHelper::addHashPrefix"
label: app.ui.id
name:
type: callback
options:
callback: "callback:strtoupper"
label: app.ui.name
```
</details>
<details open><summary>PHP</summary>
Expand All @@ -342,14 +368,18 @@ use Sylius\Bundle\GridBundle\Config\GridConfig;
return static function (GridConfig $grid): void {
$grid->addGrid(GridBuilder::create('app_user', '%app.model.user.class%')
->addField(
CallbackField::create('roles' fn (array $roles): string => implode(', ', $roles))
->setLabel('app.ui.roles') // # each filed type can have a label, we suggest using translation keys instead of messages
->setPath('roles')
CallbackField::create('id', 'App\\Helper\\GridHelper::addHashPrefix')
->setLabel('app.ui.id')
)
// or
->addField(
CallbackField::create('status' fn (array $status): string => "<strong>$status</strong>", false) // the third argument allows to disable htmlspecialchars if set to false
->setLabel('app.ui.status') // # each filed type can have a label, we suggest using translation keys instead of messages
->setPath('status')
CallbackField::create('id', ['App\\Helper\\GridHelper', 'addHashPrefix'])
->setLabel('app.ui.id')
)

->addField(
CallbackField::create('name', 'strtoupper')
->setLabel('app.ui.name')
)
)
};
Expand Down Expand Up @@ -382,14 +412,16 @@ final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
{
$gridBuilder
->addField(
CallbackField::create('roles' fn (array $roles): string => implode(', ', $roles))
->setLabel('app.ui.roles') // # each filed type can have a label, we suggest using translation keys instead of messages
->setPath('roles')
CallbackField::create('id', GridHelper::addHashPrefix(...))
->setLabel('app.ui.id')
)
->addField(
CallbackField::create('status' fn (array $status): string => "<strong>$status</strong>", false) // the third argument allows to disable htmlspecialchars if set to false
->setLabel('app.ui.status') // # each filed type can have a label, we suggest using translation keys instead of messages
->setPath('status')
CallbackField::create('name', 'strtoupper')
->setLabel('app.ui.name')
)
->addField(
CallbackField::create('roles' fn (array $roles): string => implode(', ', $roles))
->setLabel('app.ui.roles')
)
;
}
Expand All @@ -402,6 +434,3 @@ final class UserGrid extends AbstractGrid implements ResourceAwareGridInterface
```

</details>

This configuration will display each role of a customer separated with a comma.

63 changes: 63 additions & 0 deletions src/Bundle/Parser/OptionsParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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\Parser;

final class OptionsParser implements OptionsParserInterface
{
public function parseOptions(array $parameters): array
{
return array_map(
/**
* @param mixed $parameter
*
* @return mixed
*/
function ($parameter) {
if (is_array($parameter)) {
return $this->parseOptions($parameter);
}

return $this->parseOption($parameter);
},
$parameters,
);
}

/**
* @param mixed $parameter
*
* @return mixed
*/
private function parseOption($parameter)
{
if (!is_string($parameter)) {
return $parameter;
}

if (0 === strpos($parameter, 'callback:')) {
return $this->parseOptionCallback(substr($parameter, 9));
}

return $parameter;
}

private function parseOptionCallback(string $callback): \Closure
{
if (!is_callable($callback)) {
throw new \RuntimeException(\sprintf('%s is not a callable.', $callback));
}

return $callback(...);
}
}
19 changes: 19 additions & 0 deletions src/Bundle/Parser/OptionsParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?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\Parser;

interface OptionsParserInterface
{
public function parseOptions(array $parameters): array;
}
8 changes: 7 additions & 1 deletion src/Bundle/Renderer/TwigGridRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Sylius\Bundle\GridBundle\Renderer;

use Sylius\Bundle\GridBundle\Form\Registry\FormTypeRegistryInterface;
use Sylius\Bundle\GridBundle\Parser\OptionsParserInterface;
use Sylius\Component\Grid\Definition\Action;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\Definition\Filter;
Expand All @@ -36,6 +37,8 @@ final class TwigGridRenderer implements GridRendererInterface

private FormTypeRegistryInterface $formTypeRegistry;

private OptionsParserInterface $optionsParser;

private string $defaultTemplate;

private array $actionTemplates;
Expand All @@ -47,6 +50,7 @@ public function __construct(
ServiceRegistryInterface $fieldsRegistry,
FormFactoryInterface $formFactory,
FormTypeRegistryInterface $formTypeRegistry,
OptionsParserInterface $optionsParser,
string $defaultTemplate,
array $actionTemplates = [],
array $filterTemplates = [],
Expand All @@ -55,6 +59,7 @@ public function __construct(
$this->fieldsRegistry = $fieldsRegistry;
$this->formFactory = $formFactory;
$this->formTypeRegistry = $formTypeRegistry;
$this->optionsParser = $optionsParser;
$this->defaultTemplate = $defaultTemplate;
$this->actionTemplates = $actionTemplates;
$this->filterTemplates = $filterTemplates;
Expand All @@ -71,7 +76,8 @@ public function renderField(GridViewInterface $gridView, Field $field, $data)
$fieldType = $this->fieldsRegistry->get($field->getType());
$resolver = new OptionsResolver();
$fieldType->configureOptions($resolver);
$options = $resolver->resolve($field->getOptions());

$options = $resolver->resolve($this->optionsParser->parseOptions($field->getOptions()));

return $fieldType->render($field, $data, $options);
}
Expand Down
3 changes: 3 additions & 0 deletions src/Bundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,8 @@
<tag name="maker.command" />
</service>
<service id="Sylius\Bundle\GridBundle\Maker\MakeGrid" alias="sylius.grid.maker" />

<service id="sylius.grid.options_parser" class="Sylius\Bundle\GridBundle\Parser\OptionsParser" />
<service id="Sylius\Bundle\GridBundle\Parser\OptionsParserInterface" alias="sylius.grid.options_parser" />
</services>
</container>
1 change: 1 addition & 0 deletions src/Bundle/Resources/config/services/twig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<argument type="service" id="sylius.registry.grid_field" />
<argument type="service" id="form.factory" />
<argument type="service" id="sylius.form_registry.grid_filter" />
<argument type="service" id="sylius.grid.options_parser" />
<argument>@SyliusGrid/_grid.html.twig</argument>
<argument>%sylius.grid.templates.action%</argument>
<argument>%sylius.grid.templates.filter%</argument>
Expand Down
36 changes: 30 additions & 6 deletions src/Bundle/Tests/Functional/GridUiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ public function it_shows_authors_grid(): void
$this->assertCount(10, $this->getAuthorNamesFromResponse());
}

/** @test */
public function it_shows_authors_ids(): void
{
$this->client->request('GET', '/authors/?limit=100');

$ids = $this->getAuthorIdsFromResponse();

$this->assertNotEmpty($ids);
$this->assertSame(
array_filter($ids, fn (string $id) => str_starts_with($id, '#')),
$ids,
);
}

/** @test */
public function it_sorts_authors_by_name_ascending_by_default(): void
{
Expand Down Expand Up @@ -98,7 +112,7 @@ public function it_filters_books_by_title(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(1, $titles);
$this->assertSame('Book 5', $titles[0]);
$this->assertSame('BOOK 5', $titles[0]);
}

/** @test */
Expand All @@ -112,7 +126,7 @@ public function it_filters_books_by_title_with_contains(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(1, $titles);
$this->assertSame('Jurassic Park', $titles[0]);
$this->assertSame('JURASSIC PARK', $titles[0]);
}

/** @test */
Expand All @@ -125,7 +139,7 @@ public function it_filters_books_by_author(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(2, $titles);
$this->assertSame('Jurassic Park', $titles[0]);
$this->assertSame('JURASSIC PARK', $titles[0]);
}

/** @test */
Expand All @@ -139,7 +153,7 @@ public function it_filters_books_by_authors(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(3, $titles);
$this->assertSame('A Study in Scarlet', $titles[0]);
$this->assertSame('A STUDY IN SCARLET', $titles[0]);
}

/** @test */
Expand All @@ -152,7 +166,7 @@ public function it_filters_books_by_authors_nationality(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(2, $titles);
$this->assertSame('Jurassic Park', $titles[0]);
$this->assertSame('JURASSIC PARK', $titles[0]);
}

/** @test */
Expand All @@ -165,7 +179,7 @@ public function it_filters_books_by_author_and_currency(): void
$titles = $this->getBookTitlesFromResponse();

$this->assertCount(1, $titles);
$this->assertSame('Jurassic Park', $titles[0]);
$this->assertSame('JURASSIC PARK', $titles[0]);
}

/** @test */
Expand Down Expand Up @@ -274,6 +288,16 @@ private function getBookAuthorNationalitiesFromResponse(): array
);
}

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

/** @return string[] */
private function getAuthorNamesFromResponse(): array
{
Expand Down
5 changes: 5 additions & 0 deletions src/Bundle/spec/Renderer/TwigGridRendererSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Sylius\Bundle\GridBundle\Form\Registry\FormTypeRegistryInterface;
use Sylius\Bundle\GridBundle\Parser\OptionsParserInterface;
use Sylius\Component\Grid\Definition\Action;
use Sylius\Component\Grid\Definition\Field;
use Sylius\Component\Grid\FieldTypes\FieldTypeInterface;
Expand All @@ -35,6 +36,7 @@ function let(
ServiceRegistryInterface $fieldsRegistry,
FormFactoryInterface $formFactory,
FormTypeRegistryInterface $formTypeRegistry,
OptionsParserInterface $optionsParser,
): void {
$actionTemplates = [
'link' => '@SyliusGrid/Action/_link.html.twig',
Expand All @@ -49,6 +51,7 @@ function let(
$fieldsRegistry,
$formFactory,
$formTypeRegistry,
$optionsParser,
'"@SyliusGrid/default"',
$actionTemplates,
$filterTemplates,
Expand Down Expand Up @@ -94,6 +97,7 @@ function it_renders_a_field_with_data_via_appropriate_field_type(
Field $field,
ServiceRegistryInterface $fieldsRegistry,
FieldTypeInterface $fieldType,
OptionsParserInterface $optionsParser,
): void {
$field->getType()->willReturn('string');
$fieldsRegistry->get('string')->willReturn($fieldType);
Expand All @@ -106,6 +110,7 @@ function it_renders_a_field_with_data_via_appropriate_field_type(
$field->getOptions()->willReturn([
'foo' => 'bar',
]);
$optionsParser->parseOptions(['foo' => 'bar'])->willReturn(['foo' => 'bar']);
$fieldType->render($field, 'Value', ['foo' => 'bar'])->willReturn('<strong>Value</strong>');

$this->renderField($gridView, $field, 'Value')->shouldReturn('<strong>Value</strong>');
Expand Down
9 changes: 6 additions & 3 deletions tests/Application/config/sylius/grids.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ sylius_grid:
title: asc
fields:
title:
type: string
type: callback
options:
callback: "callback:strtoupper"
label: Title
sortable: ~
author:
Expand All @@ -60,10 +62,11 @@ sylius_grid:
name: asc
fields:
id:
type: string
type: callback
options:
callback: "callback:App\\Helper\\GridHelper::addHashPrefix"
label: ID
sortable: ~
enabled: false
name:
type: string
label: Name
Expand Down
Loading

0 comments on commit bf89482

Please sign in to comment.