Skip to content

Commit 5e6b221

Browse files
[HttpKernel] Replace ArgumentValueResolverInterface by ValueResolverInterface
1 parent eb18c02 commit 5e6b221

23 files changed

+268
-123
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Add `ControllerEvent::getAttributes()` to handle attributes on controllers
99
* Add `#[Cache]` to describe the default HTTP cache headers on controllers
1010
* Add `absolute_uri` option to surrogate fragment renderers
11+
* Add `ValueResolverInterface` and deprecate `ArgumentValueResolverInterface`
1112

1213
6.1
1314
---

Controller/ArgumentResolver.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
1717
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
1818
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver;
19+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
1920
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
2021
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
2122
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;
@@ -31,7 +32,7 @@ final class ArgumentResolver implements ArgumentResolverInterface
3132
private iterable $argumentValueResolvers;
3233

3334
/**
34-
* @param iterable<mixed, ArgumentValueResolverInterface> $argumentValueResolvers
35+
* @param iterable<mixed, ArgumentValueResolverInterface|ValueResolverInterface> $argumentValueResolvers
3536
*/
3637
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = [])
3738
{
@@ -49,32 +50,36 @@ public function getArguments(Request $request, callable $controller): array
4950

5051
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, ...$reflectors) as $metadata) {
5152
foreach ($this->argumentValueResolvers as $resolver) {
52-
if (!$resolver->supports($request, $metadata)) {
53+
if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) {
5354
continue;
5455
}
5556

56-
$resolved = $resolver->resolve($request, $metadata);
57+
$count = 0;
58+
foreach ($resolver->resolve($request, $metadata) as $argument) {
59+
++$count;
60+
$arguments[] = $argument;
61+
}
5762

58-
$atLeastOne = false;
59-
foreach ($resolved as $append) {
60-
$atLeastOne = true;
61-
$arguments[] = $append;
63+
if (1 < $count && !$metadata->isVariadic()) {
64+
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at most one value for non-variadic arguments.', get_debug_type($resolver)));
6265
}
6366

64-
if (!$atLeastOne) {
65-
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver)));
67+
if ($count) {
68+
// continue to the next controller argument
69+
continue 2;
6670
}
6771

68-
// continue to the next controller argument
69-
continue 2;
72+
if (!$resolver instanceof ValueResolverInterface) {
73+
throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver)));
74+
}
7075
}
7176

7277
$representative = $controller;
7378

7479
if (\is_array($representative)) {
7580
$representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]);
7681
} elseif (\is_object($representative)) {
77-
$representative = \get_class($representative);
82+
$representative = get_debug_type($representative);
7883
}
7984

8085
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName()));

Controller/ArgumentResolver/BackedEnumValueResolver.php

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1617
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1718
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1819

@@ -21,11 +22,18 @@
2122
* leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type.
2223
*
2324
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
25+
*
26+
* @final since Symfony 6.2
2427
*/
25-
class BackedEnumValueResolver implements ArgumentValueResolverInterface
28+
class BackedEnumValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2629
{
30+
/**
31+
* @deprecated since Symfony 6.2, use resolve() instead
32+
*/
2733
public function supports(Request $request, ArgumentMetadata $argument): bool
2834
{
35+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
36+
2937
if (!is_subclass_of($argument->getType(), \BackedEnum::class)) {
3038
return false;
3139
}
@@ -43,31 +51,43 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
4351

4452
public function resolve(Request $request, ArgumentMetadata $argument): iterable
4553
{
54+
if (!is_subclass_of($argument->getType(), \BackedEnum::class)) {
55+
return [];
56+
}
57+
58+
if ($argument->isVariadic()) {
59+
// only target route path parameters, which cannot be variadic.
60+
return [];
61+
}
62+
63+
// do not support if no value can be resolved at all
64+
// letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used
65+
// or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error.
66+
if (!$request->attributes->has($argument->getName())) {
67+
return [];
68+
}
69+
4670
$value = $request->attributes->get($argument->getName());
4771

4872
if (null === $value) {
49-
yield null;
50-
51-
return;
73+
return [null];
5274
}
5375

5476
if ($value instanceof \BackedEnum) {
55-
yield $value;
56-
57-
return;
77+
return [$value];
5878
}
5979

6080
if (!\is_int($value) && !\is_string($value)) {
61-
throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got %s.', $argument->getType(), $argument->getName(), get_debug_type($value)));
81+
throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value)));
6282
}
6383

6484
/** @var class-string<\BackedEnum> $enumType */
6585
$enumType = $argument->getType();
6686

6787
try {
68-
yield $enumType::from($value);
69-
} catch (\ValueError $error) {
70-
throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: %s', $argument->getType(), $argument->getName(), $error->getMessage()), $error);
88+
return [$enumType::from($value)];
89+
} catch (\ValueError $e) {
90+
throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e);
7191
}
7292
}
7393
}

Controller/ArgumentResolver/DateTimeValueResolver.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Attribute\MapDateTime;
1616
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
17+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1718
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1819
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1920

@@ -23,33 +24,35 @@
2324
* @author Benjamin Eberlei <kontakt@beberlei.de>
2425
* @author Tim Goudriaan <tim@codedmonkey.com>
2526
*/
26-
final class DateTimeValueResolver implements ArgumentValueResolverInterface
27+
final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2728
{
2829
/**
29-
* {@inheritdoc}
30+
* @deprecated since Symfony 6.2, use resolve() instead
3031
*/
3132
public function supports(Request $request, ArgumentMetadata $argument): bool
3233
{
34+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
35+
3336
return is_a($argument->getType(), \DateTimeInterface::class, true) && $request->attributes->has($argument->getName());
3437
}
3538

3639
/**
3740
* {@inheritdoc}
3841
*/
39-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
42+
public function resolve(Request $request, ArgumentMetadata $argument): array
4043
{
44+
if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) {
45+
return [];
46+
}
47+
4148
$value = $request->attributes->get($argument->getName());
4249

4350
if ($value instanceof \DateTimeInterface) {
44-
yield $value;
45-
46-
return;
51+
return [$value];
4752
}
4853

4954
if ($argument->isNullable() && !$value) {
50-
yield null;
51-
52-
return;
55+
return [null];
5356
}
5457

5558
$class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType();
@@ -83,6 +86,6 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
8386
throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $argument->getName()));
8487
}
8588

86-
yield $date;
89+
return [$date];
8790
}
8891
}

Controller/ArgumentResolver/DefaultValueResolver.php

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

1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1617
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1718

1819
/**
1920
* Yields the default value defined in the action signature when no value has been given.
2021
*
2122
* @author Iltar van der Berg <kjarli@gmail.com>
2223
*/
23-
final class DefaultValueResolver implements ArgumentValueResolverInterface
24+
final class DefaultValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2425
{
2526
/**
26-
* {@inheritdoc}
27+
* @deprecated since Symfony 6.2, use resolve() instead
2728
*/
2829
public function supports(Request $request, ArgumentMetadata $argument): bool
2930
{
31+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
32+
3033
return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic());
3134
}
3235

3336
/**
3437
* {@inheritdoc}
3538
*/
36-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
39+
public function resolve(Request $request, ArgumentMetadata $argument): array
3740
{
38-
yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null;
41+
if ($argument->hasDefaultValue()) {
42+
return [$argument->getDefaultValue()];
43+
}
44+
45+
if (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()) {
46+
return [null];
47+
}
48+
49+
return [];
3950
}
4051
}

Controller/ArgumentResolver/NotTaggedControllerValueResolver.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
18+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1819
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1920

2021
/**
2122
* Provides an intuitive error message when controller fails because it is not registered as a service.
2223
*
2324
* @author Simeon Kolev <simeon.kolev9@gmail.com>
2425
*/
25-
final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface
26+
final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2627
{
2728
private ContainerInterface $container;
2829

@@ -32,10 +33,12 @@ public function __construct(ContainerInterface $container)
3233
}
3334

3435
/**
35-
* {@inheritdoc}
36+
* @deprecated since Symfony 6.2, use resolve() instead
3637
*/
3738
public function supports(Request $request, ArgumentMetadata $argument): bool
3839
{
40+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
41+
3942
$controller = $request->attributes->get('_controller');
4043

4144
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
@@ -58,21 +61,28 @@ public function supports(Request $request, ArgumentMetadata $argument): bool
5861
/**
5962
* {@inheritdoc}
6063
*/
61-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
64+
public function resolve(Request $request, ArgumentMetadata $argument): array
6265
{
63-
if (\is_array($controller = $request->attributes->get('_controller'))) {
66+
$controller = $request->attributes->get('_controller');
67+
68+
if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) {
6469
$controller = $controller[0].'::'.$controller[1];
70+
} elseif (!\is_string($controller) || '' === $controller) {
71+
return [];
6572
}
6673

6774
if ('\\' === $controller[0]) {
6875
$controller = ltrim($controller, '\\');
6976
}
7077

71-
if (!$this->container->has($controller)) {
72-
$i = strrpos($controller, ':');
78+
if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) {
7379
$controller = substr($controller, 0, $i).strtolower(substr($controller, $i));
7480
}
7581

82+
if ($this->container->has($controller)) {
83+
return [];
84+
}
85+
7686
$what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller);
7787
$message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what);
7888

Controller/ArgumentResolver/RequestAttributeValueResolver.php

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

1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1617
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1718

1819
/**
1920
* Yields a non-variadic argument's value from the request attributes.
2021
*
2122
* @author Iltar van der Berg <kjarli@gmail.com>
2223
*/
23-
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface
24+
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2425
{
2526
/**
26-
* {@inheritdoc}
27+
* @deprecated since Symfony 6.2, use resolve() instead
2728
*/
2829
public function supports(Request $request, ArgumentMetadata $argument): bool
2930
{
31+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
32+
3033
return !$argument->isVariadic() && $request->attributes->has($argument->getName());
3134
}
3235

3336
/**
3437
* {@inheritdoc}
3538
*/
36-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
39+
public function resolve(Request $request, ArgumentMetadata $argument): array
3740
{
38-
yield $request->attributes->get($argument->getName());
41+
return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : [];
3942
}
4043
}

Controller/ArgumentResolver/RequestValueResolver.php

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

1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1617
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
1718

1819
/**
1920
* Yields the same instance as the request object passed along.
2021
*
2122
* @author Iltar van der Berg <kjarli@gmail.com>
2223
*/
23-
final class RequestValueResolver implements ArgumentValueResolverInterface
24+
final class RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface
2425
{
2526
/**
26-
* {@inheritdoc}
27+
* @deprecated since Symfony 6.2, use resolve() instead
2728
*/
2829
public function supports(Request $request, ArgumentMetadata $argument): bool
2930
{
31+
@trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__);
32+
3033
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class);
3134
}
3235

3336
/**
3437
* {@inheritdoc}
3538
*/
36-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
39+
public function resolve(Request $request, ArgumentMetadata $argument): array
3740
{
38-
yield $request;
41+
return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class) ? [$request] : [];
3942
}
4043
}

0 commit comments

Comments
 (0)