Skip to content

Commit 2633877

Browse files
committed
feature #46001 [HttpKernel] Add ControllerEvent::getAttributes() to handle attributes on controllers (nicolas-grekas)
This PR was squashed before being merged into the 6.2 branch. Discussion ---------- [HttpKernel] Add `ControllerEvent::getAttributes()` to handle attributes on controllers | Q | A | ------------- | --- | Branch? | 6.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Replacing #45457. Paving the way toward #44705 Commits ------- 0f2293c [HttpKernel] Add `ControllerEvent::getAttributes()` to handle attributes on controllers
2 parents 6521185 + 0f2293c commit 2633877

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 <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+
}
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 <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+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy