Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d74a72c

Browse filesBrowse files
committed
[HttpKernel] Add #[ValueResolver] for specifying a controller argument resolver
1 parent 1f7bc10 commit d74a72c
Copy full SHA for d74a72c

File tree

5 files changed

+156
-9
lines changed
Filter options

5 files changed

+156
-9
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php
+16-9Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
1515
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\AttributeValueResolver;
1617
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver;
1718
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver;
1819
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
@@ -48,38 +49,44 @@
4849
abstract_arg('argument value resolvers'),
4950
])
5051

52+
->set('argument_resolver.attribute', AttributeValueResolver::class)
53+
->args([
54+
tagged_locator('controller.argument_value_resolver', 'name'),
55+
])
56+
->tag('controller.argument_value_resolver', ['priority' => 150])
57+
5158
->set('argument_resolver.backed_enum_resolver', BackedEnumValueResolver::class)
52-
->tag('controller.argument_value_resolver', ['priority' => 100])
59+
->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => 'backed_enum'])
5360

5461
->set('argument_resolver.uid', UidValueResolver::class)
55-
->tag('controller.argument_value_resolver', ['priority' => 100])
62+
->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => 'uid'])
5663

5764
->set('argument_resolver.datetime', DateTimeValueResolver::class)
5865
->args([
5966
service('clock')->nullOnInvalid(),
6067
])
61-
->tag('controller.argument_value_resolver', ['priority' => 100])
68+
->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => 'datetime'])
6269

6370
->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class)
64-
->tag('controller.argument_value_resolver', ['priority' => 100])
71+
->tag('controller.argument_value_resolver', ['priority' => 100, 'name' => 'request_attribute'])
6572

6673
->set('argument_resolver.request', RequestValueResolver::class)
67-
->tag('controller.argument_value_resolver', ['priority' => 50])
74+
->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => 'request'])
6875

6976
->set('argument_resolver.session', SessionValueResolver::class)
70-
->tag('controller.argument_value_resolver', ['priority' => 50])
77+
->tag('controller.argument_value_resolver', ['priority' => 50, 'name' => 'session'])
7178

7279
->set('argument_resolver.service', ServiceValueResolver::class)
7380
->args([
7481
abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass'),
7582
])
76-
->tag('controller.argument_value_resolver', ['priority' => -50])
83+
->tag('controller.argument_value_resolver', ['priority' => -50, 'name' => 'service'])
7784

7885
->set('argument_resolver.default', DefaultValueResolver::class)
79-
->tag('controller.argument_value_resolver', ['priority' => -100])
86+
->tag('controller.argument_value_resolver', ['priority' => -100, 'name' => 'default'])
8087

8188
->set('argument_resolver.variadic', VariadicValueResolver::class)
82-
->tag('controller.argument_value_resolver', ['priority' => -150])
89+
->tag('controller.argument_value_resolver', ['priority' => -150, 'name' => 'variadic'])
8390

8491
->set('response_listener', ResponseListener::class)
8592
->args([
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\HttpKernel\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
15+
class ValueResolver
16+
{
17+
public function __construct(
18+
public readonly string $name,
19+
) {
20+
}
21+
}

‎src/Symfony/Component/HttpKernel/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add `#[WithHttpStatus]` for defining status codes for exceptions
1010
* Use an instance of `Psr\Clock\ClockInterface` to generate the current date time in `DateTimeValueResolver`
1111
* Add `#[WithLogLevel]` for defining log levels for exceptions
12+
* Add `#[ValueResolver]` for specifying a controller argument resolver
1213

1314
6.2
1415
---
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
17+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
18+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
20+
/**
21+
* Delegates value resolving of arguments bearing the {@see ValueResolver} attribute to the specified resolver.
22+
*
23+
* @author Mathieu Lechat <mathieu.lechat@les-tilleuls.com>
24+
*/
25+
final class AttributeValueResolver implements ValueResolverInterface
26+
{
27+
public function __construct(private readonly ContainerInterface $resolvers)
28+
{
29+
}
30+
31+
public function resolve(Request $request, ArgumentMetadata $argument): iterable
32+
{
33+
$attributes = $argument->getAttributesOfType(ValueResolver::class);
34+
35+
if (!$attributes) {
36+
return [];
37+
}
38+
39+
$resolverName = $attributes[0]->name;
40+
$resolver = $this->resolvers->get($resolverName);
41+
if (!$resolver instanceof ValueResolverInterface) {
42+
throw new \UnexpectedValueException(sprintf('"%s" resolver must implement %s, but got %s.', $resolverName, ValueResolverInterface::class, get_debug_type($resolver)));
43+
}
44+
45+
return $resolver->resolve($request, $argument);
46+
}
47+
}
+71Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
16+
use Symfony\Component\DependencyInjection\ServiceLocator;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpKernel\Attribute\ValueResolver;
19+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\AttributeValueResolver;
20+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
21+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
22+
23+
class AttributeValueResolverTest extends TestCase
24+
{
25+
public function testResolve()
26+
{
27+
$resolvedValue = ['bar'];
28+
$targetedResolver = $this->createStub(ValueResolverInterface::class);
29+
$targetedResolver->method('resolve')->willReturn($resolvedValue);
30+
$resolver = new AttributeValueResolver(new ServiceLocator(['foo' => static fn () => $targetedResolver]));
31+
32+
$request = Request::create('/');
33+
$argument = new ArgumentMetadata('dummy', null, false, false, null, false, [new ValueResolver('foo')]);
34+
35+
$this->assertEquals($resolvedValue, $resolver->resolve($request, $argument));
36+
}
37+
38+
public function testMissingAttribute()
39+
{
40+
$resolver = new AttributeValueResolver(new ServiceLocator([]));
41+
42+
$request = Request::create('/');
43+
$argument = new ArgumentMetadata('dummy', null, false, false, null);
44+
45+
$this->assertEquals([], $resolver->resolve($request, $argument));
46+
}
47+
48+
public function testMissingResolver()
49+
{
50+
$resolver = new AttributeValueResolver(new ServiceLocator([]));
51+
52+
$request = Request::create('/');
53+
$argument = new ArgumentMetadata('dummy', null, false, false, null, false, [new ValueResolver('foo')]);
54+
55+
$this->expectException(ServiceNotFoundException::class);
56+
57+
$resolver->resolve($request, $argument);
58+
}
59+
60+
public function testInvalidResolver()
61+
{
62+
$resolver = new AttributeValueResolver(new ServiceLocator(['foo' => static fn () => 'bar']));
63+
64+
$request = Request::create('/');
65+
$argument = new ArgumentMetadata('dummy', null, false, false, null, false, [new ValueResolver('foo')]);
66+
67+
$this->expectException(\UnexpectedValueException::class);
68+
69+
$resolver->resolve($request, $argument);
70+
}
71+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.