2018-02-01 20:01:12 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Routing\Loader ;
use Doctrine\Common\Annotations\Reader ;
2018-10-14 19:50:32 +00:00
use Symfony\Component\Config\Loader\LoaderInterface ;
use Symfony\Component\Config\Loader\LoaderResolverInterface ;
2018-02-01 20:01:12 +00:00
use Symfony\Component\Config\Resource\FileResource ;
use Symfony\Component\Routing\Route ;
use Symfony\Component\Routing\RouteCollection ;
/**
* AnnotationClassLoader loads routing information from a PHP class and its methods .
*
* You need to define an implementation for the getRouteDefaults () method . Most of the
* time , this method should define some PHP callable to be called for the route
* ( a controller in MVC speak ) .
*
* The @ Route annotation can be set on the class ( for global parameters ),
* and on each method .
*
* The @ Route annotation main value is the route path . The annotation also
* recognizes several parameters : requirements , options , defaults , schemes ,
* methods , host , and name . The name parameter is mandatory .
* Here is an example of how you should be able to use it :
*
* /**
* * @ Route ( " /Blog " )
* * /
* class Blog
* {
* /**
* * @ Route ( " / " , name = " blog_index " )
* * /
* public function index ()
* {
* }
*
* /**
* * @ Route ( " / { id} " , name = " blog_post " , requirements = { " id " = " \ d+ " })
* * /
* public function show ()
* {
* }
* }
*
* @ author Fabien Potencier < fabien @ symfony . com >
*/
abstract class AnnotationClassLoader implements LoaderInterface
{
protected $reader ;
/**
* @ var string
*/
protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route' ;
/**
* @ var int
*/
protected $defaultRouteIndex = 0 ;
public function __construct ( Reader $reader )
{
$this -> reader = $reader ;
}
/**
* Sets the annotation class to read route properties from .
*
* @ param string $class A fully - qualified class name
*/
public function setRouteAnnotationClass ( $class )
{
$this -> routeAnnotationClass = $class ;
}
/**
* Loads from annotations from a class .
*
* @ param string $class A class name
* @ param string | null $type The resource type
*
* @ return RouteCollection A RouteCollection instance
*
* @ throws \InvalidArgumentException When route can ' t be parsed
*/
public function load ( $class , $type = null )
{
if ( ! class_exists ( $class )) {
throw new \InvalidArgumentException ( sprintf ( 'Class "%s" does not exist.' , $class ));
}
$class = new \ReflectionClass ( $class );
if ( $class -> isAbstract ()) {
throw new \InvalidArgumentException ( sprintf ( 'Annotations from class "%s" cannot be read as it is abstract.' , $class -> getName ()));
}
$globals = $this -> getGlobals ( $class );
$collection = new RouteCollection ();
$collection -> addResource ( new FileResource ( $class -> getFileName ()));
foreach ( $class -> getMethods () as $method ) {
$this -> defaultRouteIndex = 0 ;
foreach ( $this -> reader -> getMethodAnnotations ( $method ) as $annot ) {
if ( $annot instanceof $this -> routeAnnotationClass ) {
$this -> addRoute ( $collection , $annot , $globals , $class , $method );
}
}
}
2018-06-13 18:35:28 +00:00
if ( 0 === $collection -> count () && $class -> hasMethod ( '__invoke' )) {
2019-01-18 18:33:28 +00:00
$globals = $this -> resetGlobals ();
2018-06-13 18:35:28 +00:00
foreach ( $this -> reader -> getClassAnnotations ( $class ) as $annot ) {
if ( $annot instanceof $this -> routeAnnotationClass ) {
$this -> addRoute ( $collection , $annot , $globals , $class , $class -> getMethod ( '__invoke' ));
}
}
2018-02-01 20:01:12 +00:00
}
return $collection ;
}
protected function addRoute ( RouteCollection $collection , $annot , $globals , \ReflectionClass $class , \ReflectionMethod $method )
{
$name = $annot -> getName ();
if ( null === $name ) {
$name = $this -> getDefaultRouteName ( $class , $method );
}
$name = $globals [ 'name' ] . $name ;
2019-01-18 18:33:28 +00:00
$requirements = $annot -> getRequirements ();
foreach ( $requirements as $placeholder => $requirement ) {
if ( \is_int ( $placeholder )) {
@ 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 );
}
}
2018-02-01 20:01:12 +00:00
$defaults = array_replace ( $globals [ 'defaults' ], $annot -> getDefaults ());
2019-01-18 18:33:28 +00:00
$requirements = array_replace ( $globals [ 'requirements' ], $requirements );
2018-02-01 20:01:12 +00:00
$options = array_replace ( $globals [ 'options' ], $annot -> getOptions ());
$schemes = array_merge ( $globals [ 'schemes' ], $annot -> getSchemes ());
$methods = array_merge ( $globals [ 'methods' ], $annot -> getMethods ());
$host = $annot -> getHost ();
if ( null === $host ) {
$host = $globals [ 'host' ];
}
$condition = $annot -> getCondition ();
if ( null === $condition ) {
$condition = $globals [ 'condition' ];
}
2018-10-14 19:50:32 +00:00
$path = $annot -> getLocalizedPaths () ? : $annot -> getPath ();
$prefix = $globals [ 'localized_paths' ] ? : $globals [ 'path' ];
2019-06-11 11:29:32 +00:00
$paths = [];
2018-02-01 20:01:12 +00:00
2018-10-14 19:50:32 +00:00
if ( \is_array ( $path )) {
if ( ! \is_array ( $prefix )) {
foreach ( $path as $locale => $localePath ) {
$paths [ $locale ] = $prefix . $localePath ;
}
} elseif ( $missing = array_diff_key ( $prefix , $path )) {
throw new \LogicException ( sprintf ( 'Route to "%s" is missing paths for locale(s) "%s".' , $class -> name . '::' . $method -> name , implode ( '", "' , array_keys ( $missing ))));
} else {
foreach ( $path as $locale => $localePath ) {
if ( ! isset ( $prefix [ $locale ])) {
throw new \LogicException ( sprintf ( 'Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".' , $method -> name , $locale , $class -> name ));
}
$paths [ $locale ] = $prefix [ $locale ] . $localePath ;
}
}
} elseif ( \is_array ( $prefix )) {
foreach ( $prefix as $locale => $localePrefix ) {
$paths [ $locale ] = $localePrefix . $path ;
}
} else {
$paths [] = $prefix . $path ;
}
foreach ( $method -> getParameters () as $param ) {
if ( isset ( $defaults [ $param -> name ]) || ! $param -> isDefaultValueAvailable ()) {
continue ;
}
foreach ( $paths as $locale => $path ) {
2019-06-11 11:29:32 +00:00
if ( preg_match ( sprintf ( '/\{%s(?:<.*?>)?\}/' , preg_quote ( $param -> name )), $path )) {
2018-10-14 19:50:32 +00:00
$defaults [ $param -> name ] = $param -> getDefaultValue ();
break ;
}
}
}
2018-02-01 20:01:12 +00:00
2018-10-14 19:50:32 +00:00
foreach ( $paths as $locale => $path ) {
$route = $this -> createRoute ( $path , $defaults , $requirements , $options , $host , $schemes , $methods , $condition );
$this -> configureRoute ( $route , $class , $method , $annot );
if ( 0 !== $locale ) {
$route -> setDefault ( '_locale' , $locale );
$route -> setDefault ( '_canonical_route' , $name );
$collection -> add ( $name . '.' . $locale , $route );
} else {
$collection -> add ( $name , $route );
}
}
2018-02-01 20:01:12 +00:00
}
/**
* { @ inheritdoc }
*/
public function supports ( $resource , $type = null )
{
2018-10-14 19:50:32 +00:00
return \is_string ( $resource ) && preg_match ( '/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/' , $resource ) && ( ! $type || 'annotation' === $type );
2018-02-01 20:01:12 +00:00
}
/**
* { @ inheritdoc }
*/
public function setResolver ( LoaderResolverInterface $resolver )
{
}
/**
* { @ inheritdoc }
*/
public function getResolver ()
{
}
/**
* Gets the default route name for a class method .
*
* @ param \ReflectionClass $class
* @ param \ReflectionMethod $method
*
* @ return string
*/
protected function getDefaultRouteName ( \ReflectionClass $class , \ReflectionMethod $method )
{
2019-06-11 11:29:32 +00:00
$name = str_replace ( '\\' , '_' , $class -> name ) . '_' . $method -> name ;
$name = \function_exists ( 'mb_strtolower' ) && preg_match ( '//u' , $name ) ? mb_strtolower ( $name , 'UTF-8' ) : strtolower ( $name );
2018-02-01 20:01:12 +00:00
if ( $this -> defaultRouteIndex > 0 ) {
$name .= '_' . $this -> defaultRouteIndex ;
}
++ $this -> defaultRouteIndex ;
return $name ;
}
protected function getGlobals ( \ReflectionClass $class )
{
2019-01-18 18:33:28 +00:00
$globals = $this -> resetGlobals ();
2018-02-01 20:01:12 +00:00
if ( $annot = $this -> reader -> getClassAnnotation ( $class , $this -> routeAnnotationClass )) {
if ( null !== $annot -> getName ()) {
$globals [ 'name' ] = $annot -> getName ();
}
if ( null !== $annot -> getPath ()) {
$globals [ 'path' ] = $annot -> getPath ();
}
2018-10-14 19:50:32 +00:00
$globals [ 'localized_paths' ] = $annot -> getLocalizedPaths ();
2018-02-01 20:01:12 +00:00
if ( null !== $annot -> getRequirements ()) {
$globals [ 'requirements' ] = $annot -> getRequirements ();
}
if ( null !== $annot -> getOptions ()) {
$globals [ 'options' ] = $annot -> getOptions ();
}
if ( null !== $annot -> getDefaults ()) {
$globals [ 'defaults' ] = $annot -> getDefaults ();
}
if ( null !== $annot -> getSchemes ()) {
$globals [ 'schemes' ] = $annot -> getSchemes ();
}
if ( null !== $annot -> getMethods ()) {
$globals [ 'methods' ] = $annot -> getMethods ();
}
if ( null !== $annot -> getHost ()) {
$globals [ 'host' ] = $annot -> getHost ();
}
if ( null !== $annot -> getCondition ()) {
$globals [ 'condition' ] = $annot -> getCondition ();
}
2019-01-18 18:33:28 +00:00
foreach ( $globals [ 'requirements' ] as $placeholder => $requirement ) {
if ( \is_int ( $placeholder )) {
@ 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 );
}
}
2018-02-01 20:01:12 +00:00
}
return $globals ;
}
2019-01-18 18:33:28 +00:00
private function resetGlobals ()
{
2019-06-11 11:29:32 +00:00
return [
2019-01-18 18:33:28 +00:00
'path' => null ,
2019-06-11 11:29:32 +00:00
'localized_paths' => [],
'requirements' => [],
'options' => [],
'defaults' => [],
'schemes' => [],
'methods' => [],
2019-01-18 18:33:28 +00:00
'host' => '' ,
'condition' => '' ,
'name' => '' ,
2019-06-11 11:29:32 +00:00
];
2019-01-18 18:33:28 +00:00
}
2018-02-01 20:01:12 +00:00
protected function createRoute ( $path , $defaults , $requirements , $options , $host , $schemes , $methods , $condition )
{
return new Route ( $path , $defaults , $requirements , $options , $host , $schemes , $methods , $condition );
}
abstract protected function configureRoute ( Route $route , \ReflectionClass $class , \ReflectionMethod $method , $annot );
}