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 400b10a

Browse filesBrowse files
[HttpKernel] Add ControllerEvent::getAttributes() to allow reading/defining attributes on controllers
1 parent 1f514f9 commit 400b10a
Copy full SHA for 400b10a

File tree

9 files changed

+144
-18
lines changed
Filter options

9 files changed

+144
-18
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add `ControllerEvent::getAttributes()` to allow reading/defining attributes on controllers
8+
49
6.1
510
---
611

‎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/ControllerEvent.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Event/ControllerEvent.php
+25Lines changed: 25 additions & 0 deletions
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
{
@@ -43,6 +44,30 @@ public function getController(): callable
4344

4445
public function setController(callable $controller): void
4546
{
47+
if (isset($this->controller) && $controller !== $this->controller) {
48+
$this->getRequest()->attributes->remove('_controller_reflectors');
49+
}
50+
4651
$this->controller = $controller;
4752
}
53+
54+
public function getAttributes(): array
55+
{
56+
if (isset($this->attributes) || ![$class, $action] = $this->getRequest()->attributes->get('_controller_reflectors')) {
57+
return $this->attributes ??= [];
58+
}
59+
60+
foreach (array_merge($class?->getAttributes() ?? [], $action->getAttributes()) as $attribute) {
61+
if (class_exists($attribute->getName())) {
62+
$this->attributes[$attribute->getName()][] = $attribute->newInstance();
63+
}
64+
}
65+
66+
return $this->attributes;
67+
}
68+
69+
public function setAttributes(array $attributes): void
70+
{
71+
$this->attributes = $attributes;
72+
}
4873
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/HttpKernel.php
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,23 @@ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Re
136136
throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
137137
}
138138

139+
if (!$request->attributes->has('_controller_reflectors')) {
140+
$reflector = new \ReflectionFunction($controller(...));
141+
$request->attributes->set('_controller_reflectors', [str_contains($reflector->name, '{closure}') ? null : $reflector->getClosureScopeClass(), $reflector]);
142+
}
143+
139144
$event = new ControllerEvent($this, $controller, $request, $type);
140145
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
141146
$controller = $event->getController();
142147

148+
if (!$request->attributes->has('_controller_reflectors')) {
149+
$reflector = new \ReflectionFunction($controller(...));
150+
$request->attributes->set('_controller_reflectors', [str_contains($reflector->name, '{closure}') ? null : $reflector->getClosureScopeClass(), $reflector]);
151+
}
152+
143153
// controller arguments
144154
$arguments = $this->argumentResolver->getArguments($request, $controller);
155+
$request->attributes->remove('_controller_reflectors');
145156

146157
$event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
147158
$this->dispatcher->dispatch($event, KernelEvents::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
}
+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->setAttributes([]);
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::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.