vendor/symfony/routing/Loader/AnnotationClassLoader.php line 115

Open in your IDE?
  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 Doctrine\Common\Annotations\Reader;
  12. use Symfony\Component\Config\Loader\LoaderInterface;
  13. use Symfony\Component\Config\Loader\LoaderResolverInterface;
  14. use Symfony\Component\Config\Resource\FileResource;
  15. use Symfony\Component\Routing\Annotation\Route as RouteAnnotation;
  16. use Symfony\Component\Routing\Route;
  17. use Symfony\Component\Routing\RouteCollection;
  18. use Symfony\Component\Routing\RouteCompiler;
  19. /**
  20.  * AnnotationClassLoader loads routing information from a PHP class and its methods.
  21.  *
  22.  * You need to define an implementation for the configureRoute() method. Most of the
  23.  * time, this method should define some PHP callable to be called for the route
  24.  * (a controller in MVC speak).
  25.  *
  26.  * The @Route annotation can be set on the class (for global parameters),
  27.  * and on each method.
  28.  *
  29.  * The @Route annotation main value is the route path. The annotation also
  30.  * recognizes several parameters: requirements, options, defaults, schemes,
  31.  * methods, host, and name. The name parameter is mandatory.
  32.  * Here is an example of how you should be able to use it:
  33.  *     /**
  34.  *      * @Route("/Blog")
  35.  *      * /
  36.  *     class Blog
  37.  *     {
  38.  *         /**
  39.  *          * @Route("/", name="blog_index")
  40.  *          * /
  41.  *         public function index()
  42.  *         {
  43.  *         }
  44.  *         /**
  45.  *          * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"})
  46.  *          * /
  47.  *         public function show()
  48.  *         {
  49.  *         }
  50.  *     }
  51.  *
  52.  * @author Fabien Potencier <fabien@symfony.com>
  53.  */
  54. abstract class AnnotationClassLoader implements LoaderInterface
  55. {
  56.     protected $reader;
  57.     /**
  58.      * @var string
  59.      */
  60.     protected $routeAnnotationClass 'Symfony\\Component\\Routing\\Annotation\\Route';
  61.     /**
  62.      * @var int
  63.      */
  64.     protected $defaultRouteIndex 0;
  65.     public function __construct(Reader $reader)
  66.     {
  67.         $this->reader $reader;
  68.     }
  69.     /**
  70.      * Sets the annotation class to read route properties from.
  71.      *
  72.      * @param string $class A fully-qualified class name
  73.      */
  74.     public function setRouteAnnotationClass($class)
  75.     {
  76.         $this->routeAnnotationClass $class;
  77.     }
  78.     /**
  79.      * Loads from annotations from a class.
  80.      *
  81.      * @param string      $class A class name
  82.      * @param string|null $type  The resource type
  83.      *
  84.      * @return RouteCollection A RouteCollection instance
  85.      *
  86.      * @throws \InvalidArgumentException When route can't be parsed
  87.      */
  88.     public function load($class$type null)
  89.     {
  90.         if (!class_exists($class)) {
  91.             throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.'$class));
  92.         }
  93.         $class = new \ReflectionClass($class);
  94.         if ($class->isAbstract()) {
  95.             throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.'$class->getName()));
  96.         }
  97.         $globals $this->getGlobals($class);
  98.         $collection = new RouteCollection();
  99.         $collection->addResource(new FileResource($class->getFileName()));
  100.         foreach ($class->getMethods() as $method) {
  101.             $this->defaultRouteIndex 0;
  102.             foreach ($this->reader->getMethodAnnotations($method) as $annot) {
  103.                 if ($annot instanceof $this->routeAnnotationClass) {
  104.                     $this->addRoute($collection$annot$globals$class$method);
  105.                 }
  106.             }
  107.         }
  108.         if (=== $collection->count() && $class->hasMethod('__invoke')) {
  109.             $globals $this->resetGlobals();
  110.             foreach ($this->reader->getClassAnnotations($class) as $annot) {
  111.                 if ($annot instanceof $this->routeAnnotationClass) {
  112.                     $this->addRoute($collection$annot$globals$class$class->getMethod('__invoke'));
  113.                 }
  114.             }
  115.         }
  116.         return $collection;
  117.     }
  118.     /**
  119.      * @param RouteAnnotation $annot   or an object that exposes a similar interface
  120.      * @param array           $globals
  121.      */
  122.     protected function addRoute(RouteCollection $collection$annot$globals, \ReflectionClass $class, \ReflectionMethod $method)
  123.     {
  124.         $name $annot->getName();
  125.         if (null === $name) {
  126.             $name $this->getDefaultRouteName($class$method);
  127.         }
  128.         $name $globals['name'].$name;
  129.         $requirements $annot->getRequirements();
  130.         foreach ($requirements as $placeholder => $requirement) {
  131.             if (\is_int($placeholder)) {
  132.                 @trigger_error(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?'$placeholder$requirement$name$class->getName(), $method->getName()), \E_USER_DEPRECATED);
  133.             }
  134.         }
  135.         $defaults array_replace($globals['defaults'], $annot->getDefaults());
  136.         $requirements array_replace($globals['requirements'], $requirements);
  137.         $options array_replace($globals['options'], $annot->getOptions());
  138.         $schemes array_merge($globals['schemes'], $annot->getSchemes());
  139.         $methods array_merge($globals['methods'], $annot->getMethods());
  140.         $host $annot->getHost();
  141.         if (null === $host) {
  142.             $host $globals['host'];
  143.         }
  144.         $condition $annot->getCondition();
  145.         if (null === $condition) {
  146.             $condition $globals['condition'];
  147.         }
  148.         $path $annot->getLocalizedPaths() ?: $annot->getPath();
  149.         $prefix $globals['localized_paths'] ?: $globals['path'];
  150.         $paths = [];
  151.         if (\is_array($path)) {
  152.             if (!\is_array($prefix)) {
  153.                 foreach ($path as $locale => $localePath) {
  154.                     $paths[$locale] = $prefix.$localePath;
  155.                 }
  156.             } elseif ($missing array_diff_key($prefix$path)) {
  157.                 throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".'$class->name.'::'.$method->nameimplode('", "'array_keys($missing))));
  158.             } else {
  159.                 foreach ($path as $locale => $localePath) {
  160.                     if (!isset($prefix[$locale])) {
  161.                         throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".'$method->name$locale$class->name));
  162.                     }
  163.                     $paths[$locale] = $prefix[$locale].$localePath;
  164.                 }
  165.             }
  166.         } elseif (\is_array($prefix)) {
  167.             foreach ($prefix as $locale => $localePrefix) {
  168.                 $paths[$locale] = $localePrefix.$path;
  169.             }
  170.         } else {
  171.             $paths[] = $prefix.$path;
  172.         }
  173.         foreach ($method->getParameters() as $param) {
  174.             if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) {
  175.                 continue;
  176.             }
  177.             foreach ($paths as $locale => $path) {
  178.                 if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/'preg_quote($param->name)), $path)) {
  179.                     $defaults[$param->name] = $param->getDefaultValue();
  180.                     break;
  181.                 }
  182.             }
  183.         }
  184.         foreach ($paths as $locale => $path) {
  185.             $route $this->createRoute($path$defaults$requirements$options$host$schemes$methods$condition);
  186.             $this->configureRoute($route$class$method$annot);
  187.             if (!== $locale) {
  188.                 $route->setDefault('_locale'$locale);
  189.                 $route->setRequirement('_locale'preg_quote($localeRouteCompiler::REGEX_DELIMITER));
  190.                 $route->setDefault('_canonical_route'$name);
  191.                 $collection->add($name.'.'.$locale$route);
  192.             } else {
  193.                 $collection->add($name$route);
  194.             }
  195.         }
  196.     }
  197.     /**
  198.      * {@inheritdoc}
  199.      */
  200.     public function supports($resource$type null)
  201.     {
  202.         return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/'$resource) && (!$type || 'annotation' === $type);
  203.     }
  204.     /**
  205.      * {@inheritdoc}
  206.      */
  207.     public function setResolver(LoaderResolverInterface $resolver)
  208.     {
  209.     }
  210.     /**
  211.      * {@inheritdoc}
  212.      */
  213.     public function getResolver()
  214.     {
  215.     }
  216.     /**
  217.      * Gets the default route name for a class method.
  218.      *
  219.      * @return string
  220.      */
  221.     protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
  222.     {
  223.         $name str_replace('\\''_'$class->name).'_'.$method->name;
  224.         $name = \function_exists('mb_strtolower') && preg_match('//u'$name) ? mb_strtolower($name'UTF-8') : strtolower($name);
  225.         if ($this->defaultRouteIndex 0) {
  226.             $name .= '_'.$this->defaultRouteIndex;
  227.         }
  228.         ++$this->defaultRouteIndex;
  229.         return $name;
  230.     }
  231.     protected function getGlobals(\ReflectionClass $class)
  232.     {
  233.         $globals $this->resetGlobals();
  234.         if ($annot $this->reader->getClassAnnotation($class$this->routeAnnotationClass)) {
  235.             if (null !== $annot->getName()) {
  236.                 $globals['name'] = $annot->getName();
  237.             }
  238.             if (null !== $annot->getPath()) {
  239.                 $globals['path'] = $annot->getPath();
  240.             }
  241.             $globals['localized_paths'] = $annot->getLocalizedPaths();
  242.             if (null !== $annot->getRequirements()) {
  243.                 $globals['requirements'] = $annot->getRequirements();
  244.             }
  245.             if (null !== $annot->getOptions()) {
  246.                 $globals['options'] = $annot->getOptions();
  247.             }
  248.             if (null !== $annot->getDefaults()) {
  249.                 $globals['defaults'] = $annot->getDefaults();
  250.             }
  251.             if (null !== $annot->getSchemes()) {
  252.                 $globals['schemes'] = $annot->getSchemes();
  253.             }
  254.             if (null !== $annot->getMethods()) {
  255.                 $globals['methods'] = $annot->getMethods();
  256.             }
  257.             if (null !== $annot->getHost()) {
  258.                 $globals['host'] = $annot->getHost();
  259.             }
  260.             if (null !== $annot->getCondition()) {
  261.                 $globals['condition'] = $annot->getCondition();
  262.             }
  263.             foreach ($globals['requirements'] as $placeholder => $requirement) {
  264.                 if (\is_int($placeholder)) {
  265.                     @trigger_error(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?'$placeholder$requirement$class->getName()), \E_USER_DEPRECATED);
  266.                 }
  267.             }
  268.         }
  269.         return $globals;
  270.     }
  271.     private function resetGlobals()
  272.     {
  273.         return [
  274.             'path' => null,
  275.             'localized_paths' => [],
  276.             'requirements' => [],
  277.             'options' => [],
  278.             'defaults' => [],
  279.             'schemes' => [],
  280.             'methods' => [],
  281.             'host' => '',
  282.             'condition' => '',
  283.             'name' => '',
  284.         ];
  285.     }
  286.     protected function createRoute($path$defaults$requirements$options$host$schemes$methods$condition)
  287.     {
  288.         return new Route($path$defaults$requirements$options$host$schemes$methods$condition);
  289.     }
  290.     abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method$annot);
  291. }