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

Skip to content

Commit c3aef36

Browse files
committed
[Routing] PSR-4 directory loader
1 parent c2ad569 commit c3aef36

20 files changed

+445
-46
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+12
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@
196196
use Symfony\Component\RateLimiter\Storage\CacheStorage;
197197
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
198198
use Symfony\Component\Routing\Loader\AnnotationFileLoader;
199+
use Symfony\Component\Routing\Loader\Psr4DirectoryLoader;
199200
use Symfony\Component\Security\Core\AuthenticationEvents;
200201
use Symfony\Component\Security\Core\Exception\AuthenticationException;
201202
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
@@ -1180,6 +1181,17 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co
11801181
new Reference('file_locator'),
11811182
new Reference('routing.loader.annotation'),
11821183
]);
1184+
1185+
$container->register('routing.loader.psr4', Psr4DirectoryLoader::class)
1186+
->setPublic(false)
1187+
->addTag('routing.loader', ['priority' => -10])
1188+
->setArguments([
1189+
new Reference('file_locator'),
1190+
]);
1191+
1192+
if (!class_exists(Psr4DirectoryLoader::class)) {
1193+
$container->removeDefinition('routing.loader.psr4');
1194+
}
11831195
}
11841196

11851197
private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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\Bundle\FrameworkBundle\Tests\Functional;
13+
14+
abstract class AbstractAttributeRoutingTest extends AbstractWebTestCase
15+
{
16+
/**
17+
* @dataProvider getRoutes
18+
*/
19+
public function testAnnotatedController(string $path, string $expectedValue)
20+
{
21+
$client = $this->createClient(['test_case' => $this->getTestCaseApp(), 'root_config' => 'config.yml']);
22+
$client->request('GET', '/annotated'.$path);
23+
24+
$this->assertSame(200, $client->getResponse()->getStatusCode());
25+
$this->assertSame($expectedValue, $client->getResponse()->getContent());
26+
27+
$router = self::getContainer()->get('router');
28+
29+
$this->assertSame('/annotated/create-transaction', $router->generate('symfony_framework_tests_functional_test_annotated_createtransaction'));
30+
}
31+
32+
public function getRoutes(): array
33+
{
34+
return [
35+
['/null_request', 'Symfony\Component\HttpFoundation\Request'],
36+
['/null_argument', ''],
37+
['/null_argument_with_route_param', ''],
38+
['/null_argument_with_route_param/value', 'value'],
39+
['/argument_with_route_param_and_default', 'value'],
40+
['/argument_with_route_param_and_default/custom', 'custom'],
41+
];
42+
}
43+
44+
abstract protected function getTestCaseApp(): string;
45+
}

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/AnnotatedControllerTest.php

+3-26
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,10 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
1313

14-
class AnnotatedControllerTest extends AbstractWebTestCase
14+
class AnnotatedControllerTest extends AbstractAttributeRoutingTest
1515
{
16-
/**
17-
* @dataProvider getRoutes
18-
*/
19-
public function testAnnotatedController($path, $expectedValue)
16+
protected function getTestCaseApp(): string
2017
{
21-
$client = $this->createClient(['test_case' => 'AnnotatedController', 'root_config' => 'config.yml']);
22-
$client->request('GET', '/annotated'.$path);
23-
24-
$this->assertSame(200, $client->getResponse()->getStatusCode());
25-
$this->assertSame($expectedValue, $client->getResponse()->getContent());
26-
27-
$router = self::getContainer()->get('router');
28-
29-
$this->assertSame('/annotated/create-transaction', $router->generate('symfony_framework_tests_functional_test_annotated_createtransaction'));
30-
}
31-
32-
public function getRoutes()
33-
{
34-
return [
35-
['/null_request', 'Symfony\Component\HttpFoundation\Request'],
36-
['/null_argument', ''],
37-
['/null_argument_with_route_param', ''],
38-
['/null_argument_with_route_param/value', 'value'],
39-
['/argument_with_route_param_and_default', 'value'],
40-
['/argument_with_route_param_and_default/custom', 'custom'],
41-
];
18+
return 'AnnotatedController';
4219
}
4320
}

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/AnnotatedController.php

+10-20
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,32 @@
1717

1818
class AnnotatedController
1919
{
20-
/**
21-
* @Route("/null_request", name="null_request")
22-
*/
23-
public function requestDefaultNullAction(Request $request = null)
20+
#[Route('/null_request', name: 'null_request')]
21+
public function requestDefaultNullAction(Request $request = null): Response
2422
{
2523
return new Response($request ? $request::class : null);
2624
}
2725

28-
/**
29-
* @Route("/null_argument", name="null_argument")
30-
*/
31-
public function argumentDefaultNullWithoutRouteParamAction($value = null)
26+
#[Route('/null_argument', name: 'null_argument')]
27+
public function argumentDefaultNullWithoutRouteParamAction($value = null): Response
3228
{
3329
return new Response($value);
3430
}
3531

36-
/**
37-
* @Route("/null_argument_with_route_param/{value}", name="null_argument_with_route_param")
38-
*/
39-
public function argumentDefaultNullWithRouteParamAction($value = null)
32+
#[Route('/null_argument_with_route_param/{value}', name: 'null_argument_with_route_param')]
33+
public function argumentDefaultNullWithRouteParamAction($value = null): Response
4034
{
4135
return new Response($value);
4236
}
4337

44-
/**
45-
* @Route("/argument_with_route_param_and_default/{value}", defaults={"value": "value"}, name="argument_with_route_param_and_default")
46-
*/
47-
public function argumentWithoutDefaultWithRouteParamAndDefaultAction($value)
38+
#[Route('/argument_with_route_param_and_default/{value}', defaults: ['value' => 'value'], name: 'argument_with_route_param_and_default')]
39+
public function argumentWithoutDefaultWithRouteParamAndDefaultAction($value): Response
4840
{
4941
return new Response($value);
5042
}
5143

52-
/**
53-
* @Route("/create-transaction")
54-
*/
55-
public function createTransaction()
44+
#[Route('/create-transaction')]
45+
public function createTransaction(): Response
5646
{
5747
return new Response();
5848
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\Bundle\FrameworkBundle\Tests\Functional;
13+
14+
use Symfony\Component\Routing\Loader\Psr4DirectoryLoader;
15+
16+
final class Psr4RoutingTest extends AbstractAttributeRoutingTest
17+
{
18+
public static function setUpBeforeClass(): void
19+
{
20+
if (!class_exists(Psr4DirectoryLoader::class)) {
21+
self::markTestSkipped('This test requires symfony/routing >= 6.2.');
22+
}
23+
24+
parent::setUpBeforeClass();
25+
}
26+
27+
protected function getTestCaseApp(): string
28+
{
29+
return 'Psr4Routing';
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
13+
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;
14+
15+
return [
16+
new FrameworkBundle(),
17+
new TestBundle(),
18+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
imports:
2+
- { resource: ../config/default.yml }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
test_bundle:
2+
prefix: /annotated
3+
resource: "@TestBundle/Controller"
4+
type: psr4@Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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\Routing\Loader;
13+
14+
use Symfony\Component\Config\FileLocatorInterface;
15+
use Symfony\Component\Config\Loader\FileLoader;
16+
use Symfony\Component\Routing\RouteCollection;
17+
18+
/**
19+
* A loader that discovers controller classes in a directory that follows PSR-4.
20+
*
21+
* @author Alexander M. Turek <[email protected]>
22+
*/
23+
final class Psr4DirectoryLoader extends FileLoader
24+
{
25+
public function __construct(FileLocatorInterface $locator)
26+
{
27+
// PSR-4 directory loader has no env-aware logic, so we drop the $env constructor parameter.
28+
parent::__construct($locator);
29+
}
30+
31+
public function load(mixed $resource, string $type = null): ?RouteCollection
32+
{
33+
$path = $this->locator->locate($resource);
34+
if (!is_dir($path)) {
35+
return new RouteCollection();
36+
}
37+
38+
return $this->loadFromDirectory($path, trim(substr($type, 5), ' \\'));
39+
}
40+
41+
public function supports(mixed $resource, string $type = null): bool
42+
{
43+
return \is_string($resource) && null !== $type && str_starts_with($type, 'psr4@');
44+
}
45+
46+
private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection
47+
{
48+
$collection = new RouteCollection();
49+
50+
/** @var \SplFileInfo $file */
51+
foreach (new \FilesystemIterator($directory) as $file) {
52+
if ($file->isDir()) {
53+
$collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename()));
54+
55+
continue;
56+
}
57+
if ('php' !== $file->getExtension() || !class_exists($className = $psr4Prefix.'\\'.$file->getBasename('.php'))) {
58+
continue;
59+
}
60+
61+
$collection->addCollection($this->import($className, 'attribute'));
62+
}
63+
64+
return $collection;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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\Routing\Tests\Fixtures\Psr4Controllers;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\Routing\Annotation\Route;
16+
17+
#[Route('/my/route', name: 'my_route')]
18+
final class MyController
19+
{
20+
public function __invoke(): Response
21+
{
22+
return new Response(status: Response::HTTP_NO_CONTENT);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Routing\Tests\Fixtures\Psr4Controllers;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
final class MyUnannotatedController
17+
{
18+
public function myAction(): Response
19+
{
20+
return new Response(status: Response::HTTP_NO_CONTENT);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\Routing\Tests\Fixtures\Psr4Controllers\SubNamespace\EvenDeeperNamespace;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\Routing\Annotation\Route;
16+
17+
#[Route('/my/other/route', name: 'my_other_controller_', methods: ['PUT'])]
18+
final class MyOtherController
19+
{
20+
#[Route('/first', name: 'one')]
21+
public function firstAction(): Response
22+
{
23+
return new Response(status: Response::HTTP_NO_CONTENT);
24+
}
25+
26+
#[Route('/second', name: 'two')]
27+
public function secondAction(): Response
28+
{
29+
return new Response(status: Response::HTTP_NO_CONTENT);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\SubNamespace;;
6+
7+
interface IrrelevantInterface
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\Routing\Tests\Fixtures\Psr4Controllers\SubNamespace;
4+
5+
use Symfony\Component\Routing\Annotation\Route;
6+
7+
#[Route('/my/controller/with/a/trait', name: 'my_controller_')]
8+
final class MyControllerWithATrait implements IrrelevantInterface
9+
{
10+
use SomeSharedImplementation;
11+
}

0 commit comments

Comments
 (0)