Skip to content

Commit 19d874a

Browse files
authored
update CallableFieldType to work with services (#368)
Uptdate the CallableFieldType so it can be used with services. Available services are the one tagged with `sylius.grid_field_callable_service`. The documentation for this can be found in the second commit of this PR Sylius/Stack#245
2 parents a0387f3 + 2d54925 commit 19d874a

File tree

3 files changed

+137
-5
lines changed

3 files changed

+137
-5
lines changed
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+
}

FieldTypes/CallableFieldType.php

Lines changed: 45 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,45 @@ 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+
if (isset($options['method'])) {
68+
$callable = [$service, $options['method']];
69+
70+
if (!is_callable($callable)) {
71+
throw new \RuntimeException(sprintf('The method "%s" is not callable on service "%s".', $options['method'], $options['service']));
72+
}
73+
74+
return $callable;
75+
}
76+
77+
if (!is_callable($service)) {
78+
throw new \RuntimeException(sprintf('The service "%s" is not callable.', $options['service']));
79+
}
80+
81+
return $service;
82+
}
83+
4984
public function configureOptions(OptionsResolver $resolver): void
5085
{
51-
$resolver->setRequired('callable');
86+
$resolver->setDefined('callable');
5287
$resolver->setAllowedTypes('callable', 'callable');
5388

89+
$resolver->setDefined('service');
90+
$resolver->setAllowedTypes('service', 'string');
91+
92+
$resolver->setDefined('method');
93+
$resolver->setAllowedTypes('method', 'string');
94+
5495
$resolver->setDefault('htmlspecialchars', true);
5596
$resolver->setAllowedTypes('htmlspecialchars', 'bool');
5697
}

spec/FieldTypes/CallableFieldTypeSpec.php

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,30 @@
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(['my_service' => fn () => new class() {
31+
public function __invoke(string $value): string
32+
{
33+
return strtoupper($value);
34+
}
35+
36+
public function concatenate(array $value): string
37+
{
38+
return implode(', ', $value);
39+
}
40+
},
41+
]) implements ServiceProviderInterface {
42+
use ServiceLocatorTrait;
43+
},
44+
);
2745
}
2846

2947
function it_is_a_grid_field_type(): void
@@ -79,6 +97,31 @@ function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_static_callabl
7997
])->shouldReturn('bar');
8098
}
8199

100+
function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_service(
101+
DataExtractorInterface $dataExtractor,
102+
Field $field,
103+
): void {
104+
$dataExtractor->get($field, ['foo' => 'bar'])->willReturn('bar');
105+
106+
$this->render($field, ['foo' => 'bar'], [
107+
'service' => 'my_service',
108+
'htmlspecialchars' => true,
109+
])->shouldReturn('BAR');
110+
}
111+
112+
function it_uses_data_extractor_to_obtain_data_and_passes_it_to_a_service_and_method(
113+
DataExtractorInterface $dataExtractor,
114+
Field $field,
115+
): void {
116+
$dataExtractor->get($field, ['foo' => ['foo', 'bar', 'foobar']])->willReturn(['foo', 'bar', 'foobar']);
117+
118+
$this->render($field, ['foo' => ['foo', 'bar', 'foobar']], [
119+
'service' => 'my_service',
120+
'method' => 'concatenate',
121+
'htmlspecialchars' => true,
122+
])->shouldReturn('foo, bar, foobar');
123+
}
124+
82125
function it_throws_an_exception_when_a_callable_return_value_cannot_be_casted_to_string(
83126
DataExtractorInterface $dataExtractor,
84127
Field $field,
@@ -98,6 +141,35 @@ function it_throws_an_exception_when_a_callable_return_value_cannot_be_casted_to
98141
]);
99142
}
100143

144+
function it_throws_an_exception_when_neither_callable_nor_service_options_are_defined(
145+
DataExtractorInterface $dataExtractor,
146+
Field $field,
147+
): void {
148+
$this
149+
->shouldThrow(\RuntimeException::class)
150+
->during('render', [
151+
$field,
152+
['foo' => 'bar'],
153+
[],
154+
]);
155+
}
156+
157+
function it_throws_an_exception_when_both_callable_and_service_options_are_defined(
158+
DataExtractorInterface $dataExtractor,
159+
Field $field,
160+
): void {
161+
$this
162+
->shouldThrow(\RuntimeException::class)
163+
->during('render', [
164+
$field,
165+
['foo' => 'bar'],
166+
[
167+
'callable' => fn () => new \stdclass(),
168+
'service' => 'my_service',
169+
],
170+
]);
171+
}
172+
101173
static function callable(mixed $value): string
102174
{
103175
return strtolower($value);

0 commit comments

Comments
 (0)