Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0f2293c

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

File tree

11 files changed

+208
-28
lines changed

11 files changed

+208
-28
lines changed

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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
}
Lines 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 <[email protected]>
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+
}
Lines 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 <[email protected]>
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

Lines 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)