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

Skip to content

Commit 56c40dd

Browse files
feature #63090 [HttpKernel] Return attributes as a flat list when using Controller[Arguments]Event::getAttributes(*) (nicolas-grekas)
This PR was merged into the 8.1 branch. Discussion ---------- [HttpKernel] Return attributes as a flat list when using `Controller[Arguments]Event::getAttributes(*)` | Q | A | ------------- | --- | Branch? | 8.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT This will give more flexibility and more accuracy to process controller attributes. Commits ------- 6098bbd [HttpKernel] Return attributes as a flat list when using `Controller[Arguments]Event::getAttributes(*)`
2 parents f5db101 + 6098bbd commit 56c40dd

14 files changed

Lines changed: 134 additions & 184 deletions

UPGRADE-8.1.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ FrameworkBundle
2424

2525
* Deprecate setting the `framework.profiler.collect_serializer_data` config option
2626

27+
HttpKernel
28+
----------
29+
30+
* Deprecate passing a non-flat list of attributes to `Controller::setController()`
31+
2732
Uid
2833
---
2934

src/Symfony/Bridge/Twig/EventListener/TemplateAttributeListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function onKernelView(ViewEvent $event): void
3737
}
3838
$attribute = $event->getRequest()->attributes->get('_template');
3939

40-
if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes()[Template::class][0] ?? null) {
40+
if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes(Template::class)[0] ?? null) {
4141
return;
4242
}
4343

src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function removeSubscriber(EventSubscriberInterface $subscriber): void;
4848
/**
4949
* Gets the listeners of a specific event or all listeners sorted by descending priority.
5050
*
51-
* @return array<callable[]|callable>
51+
* @return ($eventName is null ? array<callable[]> : array<callable>)
5252
*/
5353
public function getListeners(?string $eventName = null): array;
5454

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ CHANGELOG
77
* Add support for `UploadedFile` when using `MapRequestPayload`
88
* Add support for bundles as compiler pass
99
* Add support for `SOURCE_DATE_EPOCH` environment variable
10-
* Add `ResponseEvent::getControllerAttributes()`
10+
* Add property `$controllerArgumentsEvent` to `ResponseEvent`
1111
* Add `Request` attribute `_controller_attributes` to decouple controller attributes from their source code
12+
* Return attributes as a flat list when using `Controller[Arguments]Event::getAttributes('*')`
1213
* Pass `request` and `args` variables to `Cache` attribute expressions containing the `Request` object and controller arguments
1314
* Allow using closures with the `Cache` attribute
1415
* Allow setting a condition when the `Cache` attribute should be applied
16+
* Deprecate passing a non-flat list of attributes to `Controller::setController()`
1517

1618
8.0
1719
---

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function getController(): callable
5353
}
5454

5555
/**
56-
* @param array<class-string, list<object>>|null $attributes
56+
* @param list<object>|null $attributes
5757
*/
5858
public function setController(callable $controller, ?array $attributes = null): void
5959
{
@@ -99,9 +99,9 @@ public function getNamedArguments(): array
9999
/**
100100
* @template T of object
101101
*
102-
* @param class-string<T>|null $className
102+
* @param class-string<T>|'*'|null $className
103103
*
104-
* @return ($className is null ? array<class-string, list<object>> : list<T>)
104+
* @return ($className is null ? array<class-string, list<object>> : ($className is '*' ? list<object> : list<T>))
105105
*/
106106
public function getAttributes(?string $className = null): array
107107
{

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

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,21 @@ public function getControllerReflector(): \ReflectionFunctionAbstract
4848
}
4949

5050
/**
51-
* @param array<class-string, list<object>>|null $attributes
51+
* @param list<object>|null $attributes
5252
*/
5353
public function setController(callable $controller, ?array $attributes = null): void
5454
{
5555
if (null !== $attributes) {
56-
$this->getRequest()->attributes->set('_controller_attributes', $attributes);
56+
if (!array_is_list($flattenAttributes = $attributes)) {
57+
trigger_deprecation('symfony/http-kernel', '8.1', 'Passing an array of attributes grouped by class name to "%s()" is deprecated. Pass a flat list of attributes instead.', __METHOD__);
58+
$flattenAttributes = [];
59+
foreach ($attributes as $attributes) {
60+
foreach (\is_array($attributes) ? $attributes : [$attributes] as $attribute) {
61+
$flattenAttributes[] = $attribute;
62+
}
63+
}
64+
}
65+
$this->getRequest()->attributes->set('_controller_attributes', $flattenAttributes);
5766
}
5867

5968
if (isset($this->controller) && ($controller instanceof \Closure ? $controller == $this->controller : $controller === $this->controller)) {
@@ -66,47 +75,54 @@ public function setController(callable $controller, ?array $attributes = null):
6675
$this->getRequest()->attributes->remove('_controller_attributes');
6776
}
6877

69-
if (\is_array($controller) && method_exists(...$controller)) {
70-
$this->controllerReflector = new \ReflectionMethod(...$controller);
71-
} elseif (\is_string($controller) && str_contains($controller, '::')) {
72-
$this->controllerReflector = new \ReflectionMethod(...explode('::', $controller, 2));
73-
} else {
74-
$this->controllerReflector = new \ReflectionFunction($controller(...));
75-
}
78+
$this->controllerReflector = match (true) {
79+
\is_array($controller) && method_exists(...$controller) => new \ReflectionMethod(...$controller),
80+
\is_string($controller) && str_contains($controller, '::') => new \ReflectionMethod(...explode('::', $controller, 2)),
81+
default => new \ReflectionFunction($controller(...)),
82+
};
7683

7784
$this->controller = $controller;
7885
}
7986

8087
/**
8188
* @template T of object
8289
*
83-
* @param class-string<T>|null $className
90+
* @param class-string<T>|'*'|null $className
8491
*
85-
* @return ($className is null ? array<class-string, list<object>> : list<T>)
92+
* @return ($className is null ? array<class-string, list<object>> : ($className is '*' ? list<object> : list<T>))
8693
*/
8794
public function getAttributes(?string $className = null): array
8895
{
89-
if (null !== $attributes = $this->getRequest()->attributes->get('_controller_attributes')) {
90-
return null === $className ? $attributes : $attributes[$className] ?? [];
96+
if (null === $attributes = $this->getRequest()->attributes->get('_controller_attributes')) {
97+
$class = match (true) {
98+
\is_array($this->controller) && method_exists(...$this->controller) => new \ReflectionClass($this->controller[0]),
99+
\is_string($this->controller) && false !== $i = strpos($this->controller, '::') => new \ReflectionClass(substr($this->controller, 0, $i)),
100+
$this->controllerReflector instanceof \ReflectionFunction => $this->controllerReflector->isAnonymous() ? null : $this->controllerReflector->getClosureCalledClass(),
101+
};
102+
$attributes = [];
103+
104+
foreach (array_merge($class?->getAttributes() ?? [], $this->controllerReflector->getAttributes()) as $attribute) {
105+
if (class_exists($attribute->getName())) {
106+
$attributes[] = $attribute->newInstance();
107+
}
108+
}
109+
110+
$this->getRequest()->attributes->set('_controller_attributes', $attributes);
91111
}
92112

93-
if (\is_array($this->controller) && method_exists(...$this->controller)) {
94-
$class = new \ReflectionClass($this->controller[0]);
95-
} elseif (\is_string($this->controller) && false !== $i = strpos($this->controller, '::')) {
96-
$class = new \ReflectionClass(substr($this->controller, 0, $i));
97-
} else {
98-
$class = $this->controllerReflector instanceof \ReflectionFunction && $this->controllerReflector->isAnonymous() ? null : $this->controllerReflector->getClosureCalledClass();
113+
if ('*' === $className) {
114+
return $attributes;
99115
}
100-
$attributes = [];
101116

102-
foreach (array_merge($class?->getAttributes() ?? [], $this->controllerReflector->getAttributes()) as $attribute) {
103-
if (class_exists($attribute->getName())) {
104-
$attributes[$attribute->getName()][] = $attribute->newInstance();
105-
}
117+
if (null !== $className) {
118+
return array_values(array_filter($attributes, static fn ($attr) => $attr instanceof $className));
106119
}
107120

108-
$this->getRequest()->attributes->set('_controller_attributes', $attributes);
121+
$grouped = [];
122+
foreach ($attributes as $attribute) {
123+
$grouped[$attribute::class][] = $attribute;
124+
}
109125

110-
return null === $className ? $attributes : $attributes[$className] ?? [];
126+
return $grouped;
111127
}
112128
}

src/Symfony/Component/HttpKernel/Event/ResponseEvent.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(
3131
Request $request,
3232
int $requestType,
3333
private Response $response,
34-
private ?ControllerEvent $controllerEvent = null,
34+
public readonly ?ControllerArgumentsEvent $controllerArgumentsEvent = null,
3535
) {
3636
parent::__construct($kernel, $request, $requestType);
3737
}
@@ -45,18 +45,4 @@ public function setResponse(Response $response): void
4545
{
4646
$this->response = $response;
4747
}
48-
49-
/**
50-
* @template T of class-string|null
51-
*
52-
* @param T $className
53-
*
54-
* @return array<class-string, list<object>>|list<object>
55-
*
56-
* @psalm-return (T is null ? array<class-string, list<object>> : list<object>)
57-
*/
58-
public function getControllerAttributes(?string $className = null): array
59-
{
60-
return $this->controllerEvent?->getAttributes($className) ?? [];
61-
}
6248
}

src/Symfony/Component/HttpKernel/HttpKernel.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Re
167167
throw new NotFoundHttpException(\sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
168168
}
169169

170-
$controllerEvent = $event = new ControllerEvent($this, $controller, $request, $type);
170+
$event = new ControllerEvent($this, $controller, $request, $type);
171171
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
172172
$controller = $event->getController();
173173

174174
// controller arguments
175175
$arguments = $this->argumentResolver->getArguments($request, $controller, $event->getControllerReflector());
176176

177-
$event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type);
177+
$controllerArgumentsEvent = $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type);
178178
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
179179
$controller = $event->getController();
180180
$arguments = $event->getArguments();
@@ -201,17 +201,17 @@ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Re
201201
}
202202
}
203203

204-
return $this->filterResponse($response, $request, $type, $controllerEvent);
204+
return $this->filterResponse($response, $request, $type, $controllerArgumentsEvent);
205205
}
206206

207207
/**
208208
* Filters a response object.
209209
*
210210
* @throws \RuntimeException if the passed object is not a Response instance
211211
*/
212-
private function filterResponse(Response $response, Request $request, int $type, ?ControllerEvent $controllerEvent = null): Response
212+
private function filterResponse(Response $response, Request $request, int $type, ?ControllerArgumentsEvent $controllerArgumentsEvent = null): Response
213213
{
214-
$event = new ResponseEvent($this, $request, $type, $response, $controllerEvent);
214+
$event = new ResponseEvent($this, $request, $type, $response, $controllerArgumentsEvent);
215215

216216
$this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
217217

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,26 @@ public function testGetAttributes()
5959

6060
$this->assertEquals($expected, $event->getAttributes());
6161

62-
$expected[Bar::class][] = new Bar('foo');
63-
$event->setController($controller, $expected);
62+
$attributes = [
63+
new Bar('class'),
64+
new Bar('method'),
65+
new Bar('foo'),
66+
new Baz(),
67+
];
68+
$event->setController($controller, $attributes);
6469

65-
$this->assertEquals($expected, $event->getAttributes());
70+
$grouped = [
71+
Bar::class => [
72+
new Bar('class'),
73+
new Bar('method'),
74+
new Bar('foo'),
75+
],
76+
Baz::class => [
77+
new Baz(),
78+
],
79+
];
80+
$this->assertEquals($grouped, $event->getAttributes());
81+
$this->assertEquals($attributes, $event->getAttributes('*'));
6682
$this->assertSame($controllerEvent->getAttributes(), $event->getAttributes());
6783
}
6884

@@ -82,10 +98,20 @@ public function testGetAttributesByClassName()
8298

8399
$this->assertEquals($expected, $event->getAttributes(Bar::class));
84100

85-
$expected[] = new Bar('foo');
86-
$event->setController($controller, [Bar::class => $expected]);
101+
// When setting attributes, provide as flat list
102+
$flatAttributes = [
103+
new Bar('class'),
104+
new Bar('method'),
105+
new Bar('foo'),
106+
];
107+
$event->setController($controller, $flatAttributes);
87108

88-
$this->assertEquals($expected, $event->getAttributes(Bar::class));
109+
$expectedAfterSet = [
110+
new Bar('class'),
111+
new Bar('method'),
112+
new Bar('foo'),
113+
];
114+
$this->assertEquals($expectedAfterSet, $event->getAttributes(Bar::class));
89115
$this->assertSame($controllerEvent->getAttributes(Bar::class), $event->getAttributes(Bar::class));
90116
}
91117
}

src/Symfony/Component/HttpKernel/Tests/Event/ControllerEventTest.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\HttpKernel\Tests\Event;
1313

1414
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\Attributes\Group;
16+
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
1517
use PHPUnit\Framework\TestCase;
1618
use Symfony\Component\HttpFoundation\Request;
1719
use Symfony\Component\HttpFoundation\Response;
@@ -76,7 +78,11 @@ public function testControllerAttributesAreStoredInRequestAttributes()
7678
$attributes = $event->getAttributes();
7779

7880
$this->assertTrue($request->attributes->has('_controller_attributes'));
79-
$this->assertEquals($attributes, $request->attributes->get('_controller_attributes'));
81+
$stored = $request->attributes->get('_controller_attributes');
82+
$this->assertIsArray($stored);
83+
$this->assertCount(3, $stored);
84+
$this->assertIsArray($attributes);
85+
$this->assertArrayHasKey(Bar::class, $attributes);
8086
}
8187

8288
public function testSetControllerWithAttributesStoresInRequest()
@@ -85,11 +91,33 @@ public function testSetControllerWithAttributesStoresInRequest()
8591
$controller = [new AttributeController(), '__invoke'];
8692
$event = new ControllerEvent(new TestHttpKernel(), $controller, $request, HttpKernelInterface::MAIN_REQUEST);
8793

88-
$customAttributes = [Bar::class => [new Bar('custom')]];
94+
// Provide attributes as flat list
95+
$customAttributes = [new Bar('custom')];
8996

9097
$event->setController($controller, $customAttributes);
9198

92-
$this->assertEquals($customAttributes, $request->attributes->get('_controller_attributes'));
99+
$stored = $request->attributes->get('_controller_attributes');
100+
$this->assertIsArray($stored);
101+
$this->assertCount(1, $stored);
102+
$this->assertInstanceOf(Bar::class, $stored[0]);
103+
}
104+
105+
#[IgnoreDeprecations]
106+
#[Group('legacy')]
107+
public function testSetControllerWithGroupedAttributesConvertsToFlat()
108+
{
109+
$request = new Request();
110+
$controller = [new AttributeController(), '__invoke'];
111+
$event = new ControllerEvent(new TestHttpKernel(), $controller, $request, HttpKernelInterface::MAIN_REQUEST);
112+
113+
$groupedAttributes = [Bar::class => [new Bar('custom')]];
114+
115+
$event->setController($controller, $groupedAttributes);
116+
117+
$stored = $request->attributes->get('_controller_attributes');
118+
$this->assertIsArray($stored);
119+
$this->assertCount(1, $stored);
120+
$this->assertInstanceOf(Bar::class, $stored[0]);
93121
}
94122

95123
public function testSetControllerWithoutAttributesRemovesFromRequestWhenControllerChanges()
@@ -100,7 +128,7 @@ public function testSetControllerWithoutAttributesRemovesFromRequestWhenControll
100128
$event = new ControllerEvent(new TestHttpKernel(), $controller1, $request, HttpKernelInterface::MAIN_REQUEST);
101129

102130
// First set some attributes
103-
$customAttributes = [Bar::class => [new Bar('custom')]];
131+
$customAttributes = [new Bar('custom')];
104132
$event->setController($controller1, $customAttributes);
105133
$this->assertEquals($customAttributes, $request->attributes->get('_controller_attributes'));
106134

0 commit comments

Comments
 (0)