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 0f2293c

Browse filesBrowse files
nicolas-grekaschalasr
authored andcommitted
[HttpKernel] Add ControllerEvent::getAttributes() to handle attributes on controllers
1 parent 473b5b2 commit 0f2293c
Copy full SHA for 0f2293c

File tree

11 files changed

+208
-28
lines changed
Filter options

11 files changed

+208
-28
lines changed

‎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
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add constructor argument `bool $catchThrowable` to `HttpKernel`
8+
* Add `ControllerEvent::getAttributes()` to handle attributes on controllers
89

910
6.1
1011
---

‎src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFa
4545
public function getArguments(Request $request, callable $controller): array
4646
{
4747
$arguments = [];
48+
$reflectors = $request->attributes->get('_controller_reflectors') ?? [];
4849

49-
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
50+
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, ...$reflectors) as $metadata) {
5051
foreach ($this->argumentValueResolvers as $resolver) {
5152
if (!$resolver->supports($request, $metadata)) {
5253
continue;

‎src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/ControllerMetadata/ArgumentMetadataFactory.php
+5-12Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,15 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface
2121
/**
2222
* {@inheritdoc}
2323
*/
24-
public function createArgumentMetadata(string|object|array $controller): array
24+
public function createArgumentMetadata(string|object|array $controller, \ReflectionClass $class = null, \ReflectionFunction $reflection = null): array
2525
{
2626
$arguments = [];
2727

28-
if (\is_array($controller)) {
29-
$reflection = new \ReflectionMethod($controller[0], $controller[1]);
30-
$class = $reflection->class;
31-
} elseif (\is_object($controller) && !$controller instanceof \Closure) {
32-
$reflection = new \ReflectionMethod($controller, '__invoke');
33-
$class = $reflection->class;
34-
} else {
35-
$reflection = new \ReflectionFunction($controller);
36-
if ($class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass()) {
37-
$class = $class->name;
38-
}
28+
if (null === $reflection) {
29+
$reflection = new \ReflectionFunction($controller(...));
30+
$class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass();
3931
}
32+
$class = $class?->name;
4033

4134
foreach ($reflection->getParameters() as $param) {
4235
$attributes = [];

‎src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Event/ControllerArgumentsEvent.php
+21-6Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,32 @@
2828
*/
2929
final class ControllerArgumentsEvent extends KernelEvent
3030
{
31-
private $controller;
31+
private ControllerEvent $controllerEvent;
3232
private array $arguments;
3333

34-
public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, ?int $requestType)
34+
public function __construct(HttpKernelInterface $kernel, callable|ControllerEvent $controller, array $arguments, Request $request, ?int $requestType)
3535
{
3636
parent::__construct($kernel, $request, $requestType);
3737

38-
$this->controller = $controller;
38+
if (!$controller instanceof ControllerEvent) {
39+
$controller = new ControllerEvent($kernel, $controller, $request, $requestType);
40+
}
41+
42+
$this->controllerEvent = $controller;
3943
$this->arguments = $arguments;
4044
}
4145

4246
public function getController(): callable
4347
{
44-
return $this->controller;
48+
return $this->controllerEvent->getController();
4549
}
4650

47-
public function setController(callable $controller)
51+
/**
52+
* @param array<class-string, list<object>>|null $attributes
53+
*/
54+
public function setController(callable $controller, array $attributes = null): void
4855
{
49-
$this->controller = $controller;
56+
$this->controllerEvent->setController($controller, $attributes);
5057
}
5158

5259
public function getArguments(): array
@@ -58,4 +65,12 @@ public function setArguments(array $arguments)
5865
{
5966
$this->arguments = $arguments;
6067
}
68+
69+
/**
70+
* @return array<class-string, list<object>>
71+
*/
72+
public function getAttributes(): array
73+
{
74+
return $this->controllerEvent->getAttributes();
75+
}
6176
}

‎src/Symfony/Component/HttpKernel/Event/ControllerEvent.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Event/ControllerEvent.php
+41-1Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
final class ControllerEvent extends KernelEvent
2929
{
3030
private string|array|object $controller;
31+
private array $attributes;
3132

3233
public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType)
3334
{
@@ -41,8 +42,47 @@ public function getController(): callable
4142
return $this->controller;
4243
}
4344

44-
public function setController(callable $controller): void
45+
/**
46+
* @param array<class-string, list<object>>|null $attributes
47+
*/
48+
public function setController(callable $controller, array $attributes = null): void
4549
{
50+
if (null !== $attributes) {
51+
$this->attributes = $attributes;
52+
}
53+
54+
if (isset($this->controller) && ($controller instanceof \Closure ? $controller == $this->controller : $controller === $this->controller)) {
55+
$this->controller = $controller;
56+
57+
return;
58+
}
59+
60+
if (null === $attributes) {
61+
unset($this->attributes);
62+
}
63+
64+
$action = new \ReflectionFunction($controller(...));
65+
$this->getRequest()->attributes->set('_controller_reflectors', [str_contains($action->name, '{closure}') ? null : $action->getClosureScopeClass(), $action]);
4666
$this->controller = $controller;
4767
}
68+
69+
/**
70+
* @return array<class-string, list<object>>
71+
*/
72+
public function getAttributes(): array
73+
{
74+
if (isset($this->attributes) || ![$class, $action] = $this->getRequest()->attributes->get('_controller_reflectors')) {
75+
return $this->attributes ??= [];
76+
}
77+
78+
$this->attributes = [];
79+
80+
foreach (array_merge($class?->getAttributes() ?? [], $action->getAttributes()) as $attribute) {
81+
if (class_exists($attribute->getName())) {
82+
$this->attributes[$attribute->getName()][] = $attribute->newInstance();
83+
}
84+
}
85+
86+
return $this->attributes;
87+
}
4888
}

‎src/Symfony/Component/HttpKernel/HttpKernel.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/HttpKernel.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,11 @@ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Re
152152
// controller arguments
153153
$arguments = $this->argumentResolver->getArguments($request, $controller);
154154

155-
$event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
155+
$event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type);
156156
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
157157
$controller = $event->getController();
158158
$arguments = $event->getArguments();
159+
$request->attributes->remove('_controller_reflectors');
159160

160161
// call controller
161162
$response = $controller(...$arguments);

‎src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php
+5-5Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,23 +154,23 @@ public function testIssue41478()
154154
], $arguments);
155155
}
156156

157-
private function signature1(self $foo, array $bar, callable $baz)
157+
public function signature1(self $foo, array $bar, callable $baz)
158158
{
159159
}
160160

161-
private function signature2(self $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null)
161+
public function signature2(self $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null)
162162
{
163163
}
164164

165-
private function signature3(FakeClassThatDoesNotExist $bar, ImportedAndFake $baz)
165+
public function signature3(FakeClassThatDoesNotExist $bar, ImportedAndFake $baz)
166166
{
167167
}
168168

169-
private function signature4($foo = 'default', $bar = 500, $baz = [])
169+
public function signature4($foo = 'default', $bar = 500, $baz = [])
170170
{
171171
}
172172

173-
private function signature5(array $foo = null, $bar = null)
173+
public function signature5(array $foo = null, $bar = null)
174174
{
175175
}
176176
}

‎src/Symfony/Component/HttpKernel/Tests/Event/ControllerArgumentsEventTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/Event/ControllerArgumentsEventTest.php
+40-2Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,51 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\HttpFoundation\Request;
1616
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
17+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
18+
use Symfony\Component\HttpKernel\HttpKernelInterface;
19+
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Bar;
20+
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\AttributeController;
1721
use Symfony\Component\HttpKernel\Tests\TestHttpKernel;
1822

1923
class ControllerArgumentsEventTest extends TestCase
2024
{
2125
public function testControllerArgumentsEvent()
2226
{
23-
$filterController = new ControllerArgumentsEvent(new TestHttpKernel(), function () {}, ['test'], new Request(), 1);
24-
$this->assertEquals($filterController->getArguments(), ['test']);
27+
$event = new ControllerArgumentsEvent(new TestHttpKernel(), function () {}, ['test'], new Request(), HttpKernelInterface::MAIN_REQUEST);
28+
$this->assertEquals($event->getArguments(), ['test']);
29+
}
30+
31+
public function testSetAttributes()
32+
{
33+
$controller = function () {};
34+
$event = new ControllerArgumentsEvent(new TestHttpKernel(), $controller, ['test'], new Request(), HttpKernelInterface::MAIN_REQUEST);
35+
$event->setController($controller, []);
36+
37+
$this->assertSame([], $event->getAttributes());
38+
}
39+
40+
public function testGetAttributes()
41+
{
42+
$controller = new AttributeController();
43+
$request = new Request();
44+
45+
$controllerEvent = new ControllerEvent(new TestHttpKernel(), $controller, $request, HttpKernelInterface::MAIN_REQUEST);
46+
47+
$event = new ControllerArgumentsEvent(new TestHttpKernel(), $controllerEvent, ['test'], new Request(), HttpKernelInterface::MAIN_REQUEST);
48+
49+
$expected = [
50+
Bar::class => [
51+
new Bar('class'),
52+
new Bar('method'),
53+
],
54+
];
55+
56+
$this->assertEquals($expected, $event->getAttributes());
57+
58+
$expected[Bar::class][] = new Bar('foo');
59+
$event->setController($controller, $expected);
60+
61+
$this->assertEquals($expected, $event->getAttributes());
62+
$this->assertSame($controllerEvent->getAttributes(), $event->getAttributes());
2563
}
2664
}
+63Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\Event;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
17+
use Symfony\Component\HttpKernel\HttpKernelInterface;
18+
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Bar;
19+
use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\AttributeController;
20+
use Symfony\Component\HttpKernel\Tests\TestHttpKernel;
21+
22+
class ControllerEventTest extends TestCase
23+
{
24+
public function testSetAttributes()
25+
{
26+
$request = new Request();
27+
$request->attributes->set('_controller_reflectors', [1, 2]);
28+
$controller = [new AttributeController(), 'action'];
29+
$event = new ControllerEvent(new TestHttpKernel(), $controller, $request, HttpKernelInterface::MAIN_REQUEST);
30+
$event->setController($controller, []);
31+
32+
$this->assertSame([], $event->getAttributes());
33+
}
34+
35+
/**
36+
* @dataProvider provideGetAttributes
37+
*/
38+
public function testGetAttributes(callable $controller)
39+
{
40+
$request = new Request();
41+
$reflector = new \ReflectionFunction($controller(...));
42+
$request->attributes->set('_controller_reflectors', [str_contains($reflector->name, '{closure}') ? null : $reflector->getClosureScopeClass(), $reflector]);
43+
44+
$event = new ControllerEvent(new TestHttpKernel(), $controller, $request, HttpKernelInterface::MAIN_REQUEST);
45+
46+
$expected = [
47+
Bar::class => [
48+
new Bar('class'),
49+
new Bar('method'),
50+
],
51+
];
52+
53+
$this->assertEquals($expected, $event->getAttributes());
54+
}
55+
56+
public function provideGetAttributes()
57+
{
58+
yield [[new AttributeController(), '__invoke']];
59+
yield [new AttributeController()];
60+
yield [(new AttributeController())->__invoke(...)];
61+
yield [#[Bar('class'), Bar('method')] static function () {}];
62+
}
63+
}
+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\Tests\Fixtures\Attribute;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)]
15+
class Bar
16+
{
17+
public function __construct(
18+
public mixed $foo,
19+
) {
20+
}
21+
}

‎src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/Fixtures/Controller/AttributeController.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@
1111

1212
namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller;
1313

14+
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Bar;
1415
use Symfony\Component\HttpKernel\Tests\Fixtures\Attribute\Foo;
1516

17+
#[Bar('class'), Undefined('class')]
1618
class AttributeController
1719
{
20+
#[Bar('method'), Undefined('method')]
21+
public function __invoke()
22+
{
23+
}
24+
1825
public function action(#[Foo('bar')] string $baz)
1926
{
2027
}

0 commit comments

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