Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Routing\Loader; Chris@0: Chris@0: use Doctrine\Common\Annotations\Reader; Chris@17: use Symfony\Component\Config\Loader\LoaderInterface; Chris@17: use Symfony\Component\Config\Loader\LoaderResolverInterface; Chris@0: use Symfony\Component\Config\Resource\FileResource; Chris@0: use Symfony\Component\Routing\Route; Chris@0: use Symfony\Component\Routing\RouteCollection; Chris@0: Chris@0: /** Chris@0: * AnnotationClassLoader loads routing information from a PHP class and its methods. Chris@0: * Chris@0: * You need to define an implementation for the getRouteDefaults() method. Most of the Chris@0: * time, this method should define some PHP callable to be called for the route Chris@0: * (a controller in MVC speak). Chris@0: * Chris@0: * The @Route annotation can be set on the class (for global parameters), Chris@0: * and on each method. Chris@0: * Chris@0: * The @Route annotation main value is the route path. The annotation also Chris@0: * recognizes several parameters: requirements, options, defaults, schemes, Chris@0: * methods, host, and name. The name parameter is mandatory. Chris@0: * Here is an example of how you should be able to use it: Chris@0: * Chris@0: * /** Chris@0: * * @Route("/Blog") Chris@0: * * / Chris@0: * class Blog Chris@0: * { Chris@0: * /** Chris@0: * * @Route("/", name="blog_index") Chris@0: * * / Chris@0: * public function index() Chris@0: * { Chris@0: * } Chris@0: * Chris@0: * /** Chris@0: * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) Chris@0: * * / Chris@0: * public function show() Chris@0: * { Chris@0: * } Chris@0: * } Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: abstract class AnnotationClassLoader implements LoaderInterface Chris@0: { Chris@0: protected $reader; Chris@0: Chris@0: /** Chris@0: * @var string Chris@0: */ Chris@0: protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; Chris@0: Chris@0: /** Chris@0: * @var int Chris@0: */ Chris@0: protected $defaultRouteIndex = 0; Chris@0: Chris@0: public function __construct(Reader $reader) Chris@0: { Chris@0: $this->reader = $reader; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the annotation class to read route properties from. Chris@0: * Chris@0: * @param string $class A fully-qualified class name Chris@0: */ Chris@0: public function setRouteAnnotationClass($class) Chris@0: { Chris@0: $this->routeAnnotationClass = $class; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads from annotations from a class. Chris@0: * Chris@0: * @param string $class A class name Chris@0: * @param string|null $type The resource type Chris@0: * Chris@0: * @return RouteCollection A RouteCollection instance Chris@0: * Chris@0: * @throws \InvalidArgumentException When route can't be parsed Chris@0: */ Chris@0: public function load($class, $type = null) Chris@0: { Chris@0: if (!class_exists($class)) { Chris@0: throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); Chris@0: } Chris@0: Chris@0: $class = new \ReflectionClass($class); Chris@0: if ($class->isAbstract()) { Chris@0: throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); Chris@0: } Chris@0: Chris@0: $globals = $this->getGlobals($class); Chris@0: Chris@0: $collection = new RouteCollection(); Chris@0: $collection->addResource(new FileResource($class->getFileName())); Chris@0: Chris@0: foreach ($class->getMethods() as $method) { Chris@0: $this->defaultRouteIndex = 0; Chris@0: foreach ($this->reader->getMethodAnnotations($method) as $annot) { Chris@0: if ($annot instanceof $this->routeAnnotationClass) { Chris@0: $this->addRoute($collection, $annot, $globals, $class, $method); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@16: if (0 === $collection->count() && $class->hasMethod('__invoke')) { Chris@17: $globals = $this->resetGlobals(); Chris@16: foreach ($this->reader->getClassAnnotations($class) as $annot) { Chris@16: if ($annot instanceof $this->routeAnnotationClass) { Chris@16: $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); Chris@16: } Chris@16: } Chris@14: } Chris@14: Chris@0: return $collection; Chris@0: } Chris@0: Chris@0: protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) Chris@0: { Chris@0: $name = $annot->getName(); Chris@0: if (null === $name) { Chris@0: $name = $this->getDefaultRouteName($class, $method); Chris@0: } Chris@14: $name = $globals['name'].$name; Chris@0: Chris@0: $defaults = array_replace($globals['defaults'], $annot->getDefaults()); Chris@0: foreach ($method->getParameters() as $param) { Chris@0: if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { Chris@0: $defaults[$param->getName()] = $param->getDefaultValue(); Chris@0: } Chris@0: } Chris@0: $requirements = array_replace($globals['requirements'], $annot->getRequirements()); Chris@0: $options = array_replace($globals['options'], $annot->getOptions()); Chris@0: $schemes = array_merge($globals['schemes'], $annot->getSchemes()); Chris@0: $methods = array_merge($globals['methods'], $annot->getMethods()); Chris@0: Chris@0: $host = $annot->getHost(); Chris@0: if (null === $host) { Chris@0: $host = $globals['host']; Chris@0: } Chris@0: Chris@0: $condition = $annot->getCondition(); Chris@0: if (null === $condition) { Chris@0: $condition = $globals['condition']; Chris@0: } Chris@0: Chris@0: $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); Chris@0: Chris@0: $this->configureRoute($route, $class, $method, $annot); Chris@0: Chris@0: $collection->add($name, $route); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supports($resource, $type = null) Chris@0: { Chris@17: return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setResolver(LoaderResolverInterface $resolver) Chris@0: { Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getResolver() Chris@0: { Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the default route name for a class method. Chris@0: * Chris@0: * @param \ReflectionClass $class Chris@0: * @param \ReflectionMethod $method Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) Chris@0: { Chris@0: $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); Chris@0: if ($this->defaultRouteIndex > 0) { Chris@0: $name .= '_'.$this->defaultRouteIndex; Chris@0: } Chris@0: ++$this->defaultRouteIndex; Chris@0: Chris@0: return $name; Chris@0: } Chris@0: Chris@0: protected function getGlobals(\ReflectionClass $class) Chris@0: { Chris@17: $globals = $this->resetGlobals(); Chris@0: Chris@0: if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { Chris@14: if (null !== $annot->getName()) { Chris@14: $globals['name'] = $annot->getName(); Chris@14: } Chris@14: Chris@0: if (null !== $annot->getPath()) { Chris@0: $globals['path'] = $annot->getPath(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getRequirements()) { Chris@0: $globals['requirements'] = $annot->getRequirements(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getOptions()) { Chris@0: $globals['options'] = $annot->getOptions(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getDefaults()) { Chris@0: $globals['defaults'] = $annot->getDefaults(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getSchemes()) { Chris@0: $globals['schemes'] = $annot->getSchemes(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getMethods()) { Chris@0: $globals['methods'] = $annot->getMethods(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getHost()) { Chris@0: $globals['host'] = $annot->getHost(); Chris@0: } Chris@0: Chris@0: if (null !== $annot->getCondition()) { Chris@0: $globals['condition'] = $annot->getCondition(); Chris@0: } Chris@0: } Chris@0: Chris@0: return $globals; Chris@0: } Chris@0: Chris@17: private function resetGlobals() Chris@17: { Chris@17: return [ Chris@17: 'path' => '', Chris@17: 'requirements' => [], Chris@17: 'options' => [], Chris@17: 'defaults' => [], Chris@17: 'schemes' => [], Chris@17: 'methods' => [], Chris@17: 'host' => '', Chris@17: 'condition' => '', Chris@17: 'name' => '', Chris@17: ]; Chris@17: } Chris@17: Chris@0: protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) Chris@0: { Chris@0: return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); Chris@0: } Chris@0: Chris@0: abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); Chris@0: }