Skip to content

Commit 49f717e

Browse files
committed
wip
1 parent 9bec850 commit 49f717e

File tree

12 files changed

+196
-8
lines changed

12 files changed

+196
-8
lines changed

src/Bundle/Builder/Field/CallableField.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,18 @@ public static function create(string $name, callable $callable, bool $htmlspecia
2222
->setOption('htmlspecialchars', $htmlspecialchars)
2323
;
2424
}
25+
26+
public static function createForService(string $name, string $service, ?string $method = null, bool $htmlspecialchars = true): FieldInterface
27+
{
28+
$field = Field::create($name, 'callable')
29+
->setOption('service', $service)
30+
->setOption('htmlspecialchars', $htmlspecialchars)
31+
;
32+
33+
if ($method !== null) {
34+
$field->setOption('method', $method);
35+
}
36+
37+
return $field;
38+
}
2539
}

src/Bundle/DependencyInjection/SyliusGridExtension.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
use Sylius\Bundle\CurrencyBundle\SyliusCurrencyBundle;
1717
use Sylius\Bundle\GridBundle\Grid\GridInterface;
1818
use Sylius\Bundle\GridBundle\SyliusGridBundle;
19+
use Sylius\Component\Grid\Annotation\AsGridFieldCallableService;
1920
use Sylius\Component\Grid\Data\DataProviderInterface;
2021
use Sylius\Component\Grid\Filtering\FilterInterface;
2122
use Symfony\Component\Config\FileLocator;
23+
use Symfony\Component\DependencyInjection\ChildDefinition;
2224
use Symfony\Component\DependencyInjection\ContainerBuilder;
2325
use Symfony\Component\DependencyInjection\Extension\Extension;
2426
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@@ -67,6 +69,13 @@ public function load(array $configs, ContainerBuilder $container): void
6769
$container->registerForAutoconfiguration(DataProviderInterface::class)
6870
->addTag('sylius.grid_data_provider')
6971
;
72+
73+
$container->registerAttributeForAutoconfiguration(
74+
AsGridFieldCallableService::class,
75+
static function (ChildDefinition $definition, AsGridFieldCallableService $attribute, \Reflector $reflector): void {
76+
$definition->addTag('sylius.grid_field_callable_service');
77+
},
78+
);
7079
}
7180

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<service id="sylius.grid_field.callable" class="Sylius\Component\Grid\FieldTypes\CallableFieldType">
1919
<argument type="service" id="sylius.grid.data_extractor" />
20+
<argument type="tagged_locator" tag="sylius.grid_field_callable_service" />
2021
<tag name="sylius.grid_field" type="callable" />
2122
</service>
2223
<service id="Sylius\Component\Grid\FieldTypes\CallableFieldType" alias="sylius.grid_field.callable" />

src/Bundle/Tests/Functional/GridUiTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,20 @@ public function it_shows_authors_ids(): void
5555
);
5656
}
5757

58+
/** @test */
59+
public function it_shows_authors_nationalities(): void
60+
{
61+
$this->client->request('GET', '/authors/?limit=100');
62+
63+
$nationalities = $this->getAuthorNationalitiesFromResponse();
64+
65+
$this->assertNotEmpty($nationalities);
66+
$this->assertEquals(
67+
array_values(['EN', 'US']),
68+
array_values(array_unique($nationalities)),
69+
);
70+
}
71+
5872
/** @test */
5973
public function it_sorts_authors_by_name_ascending_by_default(): void
6074
{
@@ -298,6 +312,16 @@ private function getAuthorIdsFromResponse(): array
298312
);
299313
}
300314

315+
/** @return string[] */
316+
private function getAuthorNationalitiesFromResponse(): array
317+
{
318+
return $this->getCrawler()
319+
->filter('[data-test-nationality]')
320+
->each(
321+
fn (Crawler $node): string => $node->text(),
322+
);
323+
}
324+
301325
/** @return string[] */
302326
private function getAuthorNamesFromResponse(): array
303327
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Annotation;
15+
16+
#[\Attribute(\Attribute::TARGET_CLASS)]
17+
final class AsGridFieldCallableService
18+
{
19+
}

src/Component/FieldTypes/CallableFieldType.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,28 @@
1313

1414
namespace Sylius\Component\Grid\FieldTypes;
1515

16+
use Psr\Container\ContainerInterface;
1617
use Sylius\Component\Grid\DataExtractor\DataExtractorInterface;
1718
use Sylius\Component\Grid\Definition\Field;
1819
use Sylius\Component\Grid\Exception\UnexpectedValueException;
1920
use Symfony\Component\OptionsResolver\OptionsResolver;
2021

2122
final class CallableFieldType implements FieldTypeInterface
2223
{
23-
public function __construct(private DataExtractorInterface $dataExtractor)
24-
{
24+
public function __construct(
25+
private DataExtractorInterface $dataExtractor,
26+
private ContainerInterface $locator,
27+
) {
2528
}
2629

2730
public function render(Field $field, $data, array $options): string
2831
{
32+
if (isset($options['callable']) === isset($options['service'])) {
33+
throw new \RuntimeException('Exactly one of the "callable" or "service" options must be defined.');
34+
}
35+
2936
$value = $this->dataExtractor->get($field, $data);
30-
$value = call_user_func($options['callable'], $value);
37+
$value = call_user_func($this->getCallable($options), $value);
3138

3239
try {
3340
$value = (string) $value;
@@ -46,11 +53,36 @@ public function render(Field $field, $data, array $options): string
4653
return $value;
4754
}
4855

56+
private function getCallable(array $options): callable
57+
{
58+
if (isset($options['callable'])) {
59+
return $options['callable'];
60+
}
61+
62+
if (!$this->locator->has($options['service'])) {
63+
throw new \RuntimeException(sprintf('Service "%s" not found, make sure it is tagged with "sylius.grid_field_callable_service".', $options['service']));
64+
}
65+
66+
$service = $this->locator->get($options['service']);
67+
68+
if (isset($options['method'])) {
69+
return [$service, $options['method']];
70+
}
71+
72+
return $service;
73+
}
74+
4975
public function configureOptions(OptionsResolver $resolver): void
5076
{
51-
$resolver->setRequired('callable');
77+
$resolver->setDefined('callable');
5278
$resolver->setAllowedTypes('callable', 'callable');
5379

80+
$resolver->setDefined('service');
81+
$resolver->setAllowedTypes('service', 'string');
82+
83+
$resolver->setDefined('method');
84+
$resolver->setAllowedTypes('method', 'string');
85+
5486
$resolver->setDefault('htmlspecialchars', true);
5587
$resolver->setAllowedTypes('htmlspecialchars', 'bool');
5688
}

src/Component/spec/FieldTypes/CallableFieldTypeSpec.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,24 @@
1818
use Sylius\Component\Grid\Definition\Field;
1919
use Sylius\Component\Grid\Exception\UnexpectedValueException;
2020
use Sylius\Component\Grid\FieldTypes\FieldTypeInterface;
21+
use Symfony\Contracts\Service\ServiceLocatorTrait;
22+
use Symfony\Contracts\Service\ServiceProviderInterface;
2123

2224
final class CallableFieldTypeSpec extends ObjectBehavior
2325
{
2426
function let(DataExtractorInterface $dataExtractor): void
2527
{
26-
$this->beConstructedWith($dataExtractor);
28+
$this->beConstructedWith(
29+
$dataExtractor,
30+
new class([
31+
'my_service' => fn () => new class {
32+
public function __invoke(string $value): string { return strtoupper($value); }
33+
public function concatenate(array $value): string { return implode(', ', $value); }
34+
},
35+
]) implements ServiceProviderInterface {
36+
use ServiceLocatorTrait;
37+
},
38+
);
2739
}
2840

2941
function it_is_a_grid_field_type(): void
@@ -79,6 +91,31 @@ function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_static_callabl
7991
])->shouldReturn('bar');
8092
}
8193

94+
function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_service(
95+
DataExtractorInterface $dataExtractor,
96+
Field $field,
97+
): void {
98+
$dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar');
99+
100+
$this->render($field, ['foo' => 'bar'], [
101+
'service' => 'my_service',
102+
'htmlspecialchars' => true,
103+
])->shouldReturn('BAR');
104+
}
105+
106+
function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_service_and_method(
107+
DataExtractorInterface $dataExtractor,
108+
Field $field,
109+
): void {
110+
$dataExtractor->get($field, ['foo' => ['foo', 'bar', 'foobar']])->willReturn(['foo', 'bar', 'foobar']);
111+
112+
$this->render($field, ['foo' => ['foo', 'bar', 'foobar']], [
113+
'service' => 'my_service',
114+
'method' => 'concatenate',
115+
'htmlspecialchars' => true,
116+
])->shouldReturn('foo, bar, foobar');
117+
}
118+
82119
function it_throws_an_exception_when_a_callable_return_value_cannot_be_casted_to_string(
83120
DataExtractorInterface $dataExtractor,
84121
Field $field,
@@ -98,6 +135,35 @@ function it_throws_an_exception_when_a_callable_return_value_cannot_be_casted_to
98135
]);
99136
}
100137

138+
function it_throws_an_exception_when_neither_callable_nor_service_options_are_defined(
139+
DataExtractorInterface $dataExtractor,
140+
Field $field,
141+
): void {
142+
$this
143+
->shouldThrow(\RuntimeException::class)
144+
->during('render', [
145+
$field,
146+
['foo' => 'bar'],
147+
[],
148+
]);
149+
}
150+
151+
function it_throws_an_exception_when_both_callable_and_service_options_are_defined(
152+
DataExtractorInterface $dataExtractor,
153+
Field $field,
154+
): void {
155+
$this
156+
->shouldThrow(\RuntimeException::class)
157+
->during('render', [
158+
$field,
159+
['foo' => 'bar'],
160+
[
161+
'callable' => fn () => new \stdclass(),
162+
'service' => 'my_service'
163+
],
164+
]);
165+
}
166+
101167
static function callable(mixed $value): string
102168
{
103169
return strtolower($value);

tests/Application/config/services.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ services:
2929

3030
App\BoardGameBlog\:
3131
resource: '../src/BoardGameBlog'
32+
33+
App\Helper\GridHelper: ~

tests/Application/config/sylius/grids.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ sylius_grid:
7272
label: Name
7373
sortable: ~
7474
nationality:
75-
type: string
75+
type: callable
76+
options:
77+
service: 'App\Helper\GridHelper'
78+
method: 'formatNationality'
7679
label: Name
7780
sortable: nationality.name
7881
path: nationality

tests/Application/config/sylius/grids/author.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
->setSortable(true),
3333
)
3434
->addField(
35-
StringField::create('nationality')
35+
CallableField::createForService('nationality', \App\Helper\GridHelper::class)
3636
->setLabel('Nationality')
3737
->setSortable(true, 'nationality.name'),
3838
)

0 commit comments

Comments
 (0)