Psr4DirectoryLoader.php 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing\Loader;
  11. use Symfony\Component\Config\FileLocatorInterface;
  12. use Symfony\Component\Config\Loader\DirectoryAwareLoaderInterface;
  13. use Symfony\Component\Config\Loader\Loader;
  14. use Symfony\Component\Config\Resource\DirectoryResource;
  15. use Symfony\Component\Routing\RouteCollection;
  16. /**
  17. * A loader that discovers controller classes in a directory that follows PSR-4.
  18. *
  19. * @author Alexander M. Turek <me@derrabus.de>
  20. */
  21. final class Psr4DirectoryLoader extends Loader implements DirectoryAwareLoaderInterface
  22. {
  23. private ?string $currentDirectory = null;
  24. public function __construct(
  25. private readonly FileLocatorInterface $locator,
  26. ) {
  27. // PSR-4 directory loader has no env-aware logic, so we drop the $env constructor parameter.
  28. parent::__construct();
  29. }
  30. /**
  31. * @param array{path: string, namespace: string} $resource
  32. */
  33. public function load(mixed $resource, ?string $type = null): ?RouteCollection
  34. {
  35. $path = $this->locator->locate($resource['path'], $this->currentDirectory);
  36. if (!is_dir($path)) {
  37. return new RouteCollection();
  38. }
  39. return $this->loadFromDirectory($path, trim($resource['namespace'], '\\'));
  40. }
  41. public function supports(mixed $resource, ?string $type = null): bool
  42. {
  43. return ('attribute' === $type || 'annotation' === $type) && \is_array($resource) && isset($resource['path'], $resource['namespace']);
  44. }
  45. public function forDirectory(string $currentDirectory): static
  46. {
  47. $loader = clone $this;
  48. $loader->currentDirectory = $currentDirectory;
  49. return $loader;
  50. }
  51. private function loadFromDirectory(string $directory, string $psr4Prefix): RouteCollection
  52. {
  53. $collection = new RouteCollection();
  54. $collection->addResource(new DirectoryResource($directory, '/\.php$/'));
  55. $files = iterator_to_array(new \RecursiveIteratorIterator(
  56. new \RecursiveCallbackFilterIterator(
  57. new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
  58. fn (\SplFileInfo $current) => !str_starts_with($current->getBasename(), '.')
  59. ),
  60. \RecursiveIteratorIterator::SELF_FIRST
  61. ));
  62. usort($files, fn (\SplFileInfo $a, \SplFileInfo $b) => (string) $a > (string) $b ? 1 : -1);
  63. /** @var \SplFileInfo $file */
  64. foreach ($files as $file) {
  65. if ($file->isDir()) {
  66. $collection->addCollection($this->loadFromDirectory($file->getPathname(), $psr4Prefix.'\\'.$file->getFilename()));
  67. continue;
  68. }
  69. if ('php' !== $file->getExtension() || !class_exists($className = $psr4Prefix.'\\'.$file->getBasename('.php')) || (new \ReflectionClass($className))->isAbstract()) {
  70. continue;
  71. }
  72. $collection->addCollection($this->import($className, 'attribute'));
  73. }
  74. return $collection;
  75. }
  76. }