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

Skip to content

Commit d15dbbe

Browse files
[HttpKernel] Add ControllerEvent::getAttributes() to allow reading/defining attributes on controllers
1 parent 1f514f9 commit d15dbbe

File tree

9 files changed

+146
-19
lines changed

9 files changed

+146
-19
lines changed

src/Symfony/Component/HttpKernel/CHANGELOG.md

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

+2-1
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

+5-12
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

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,28 @@ 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+
$controllerClosure = null;
140+
if (!$request->attributes->has('_controller_reflectors')) {
141+
$reflector = new \ReflectionFunction($controllerClosure = $controller(...));
142+
$request->attributes->set('_controller_reflectors', [str_contains($reflector->name, '{closure}') ? null : $reflector->getClosureScopeClass(), $reflector]);
143+
}
144+
139145
$event = new ControllerEvent($this, $controller, $request, $type);
140146
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
141147
$controller = $event->getController();
142148

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

146158
$event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
147159
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
148-
$controller = $event->getController();
160+
$controller = ($controller !== $c = $event->getController()) ? $c : $controllerClosure ?? $c;
149161
$arguments = $event->getArguments();
150162

151163
// call controller

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

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

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