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; Chris@0: Chris@17: use Psr\Log\LoggerInterface; Chris@17: use Symfony\Component\Config\ConfigCacheFactory; Chris@17: use Symfony\Component\Config\ConfigCacheFactoryInterface; Chris@17: use Symfony\Component\Config\ConfigCacheInterface; Chris@0: use Symfony\Component\Config\Loader\LoaderInterface; Chris@17: use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; Chris@17: use Symfony\Component\HttpFoundation\Request; Chris@0: use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; Chris@17: use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; Chris@0: use Symfony\Component\Routing\Generator\UrlGeneratorInterface; Chris@17: use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; Chris@0: use Symfony\Component\Routing\Matcher\RequestMatcherInterface; Chris@0: use Symfony\Component\Routing\Matcher\UrlMatcherInterface; Chris@0: Chris@0: /** Chris@0: * The Router class is an example of the integration of all pieces of the Chris@0: * routing system for easier use. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class Router implements RouterInterface, RequestMatcherInterface Chris@0: { Chris@0: /** Chris@0: * @var UrlMatcherInterface|null Chris@0: */ Chris@0: protected $matcher; Chris@0: Chris@0: /** Chris@0: * @var UrlGeneratorInterface|null Chris@0: */ Chris@0: protected $generator; Chris@0: Chris@0: /** Chris@0: * @var RequestContext Chris@0: */ Chris@0: protected $context; Chris@0: Chris@0: /** Chris@0: * @var LoaderInterface Chris@0: */ Chris@0: protected $loader; Chris@0: Chris@0: /** Chris@0: * @var RouteCollection|null Chris@0: */ Chris@0: protected $collection; Chris@0: Chris@0: /** Chris@0: * @var mixed Chris@0: */ Chris@0: protected $resource; Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@17: protected $options = []; Chris@0: Chris@0: /** Chris@0: * @var LoggerInterface|null Chris@0: */ Chris@0: protected $logger; Chris@0: Chris@0: /** Chris@0: * @var ConfigCacheFactoryInterface|null Chris@0: */ Chris@0: private $configCacheFactory; Chris@0: Chris@0: /** Chris@0: * @var ExpressionFunctionProviderInterface[] Chris@0: */ Chris@17: private $expressionLanguageProviders = []; Chris@0: Chris@0: /** Chris@0: * @param LoaderInterface $loader A LoaderInterface instance Chris@0: * @param mixed $resource The main resource to load Chris@0: * @param array $options An array of options Chris@0: * @param RequestContext $context The context Chris@0: * @param LoggerInterface $logger A logger instance Chris@0: */ Chris@17: public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null) Chris@0: { Chris@0: $this->loader = $loader; Chris@0: $this->resource = $resource; Chris@0: $this->logger = $logger; Chris@0: $this->context = $context ?: new RequestContext(); Chris@0: $this->setOptions($options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets options. Chris@0: * Chris@0: * Available options: Chris@0: * Chris@0: * * cache_dir: The cache directory (or null to disable caching) Chris@0: * * debug: Whether to enable debugging or not (false by default) Chris@0: * * generator_class: The name of a UrlGeneratorInterface implementation Chris@0: * * generator_base_class: The base class for the dumped generator class Chris@0: * * generator_cache_class: The class name for the dumped generator class Chris@0: * * generator_dumper_class: The name of a GeneratorDumperInterface implementation Chris@0: * * matcher_class: The name of a UrlMatcherInterface implementation Chris@0: * * matcher_base_class: The base class for the dumped matcher class Chris@0: * * matcher_dumper_class: The class name for the dumped matcher class Chris@0: * * matcher_cache_class: The name of a MatcherDumperInterface implementation Chris@0: * * resource_type: Type hint for the main resource (optional) Chris@0: * * strict_requirements: Configure strict requirement checking for generators Chris@0: * implementing ConfigurableRequirementsInterface (default is true) Chris@0: * Chris@0: * @param array $options An array of options Chris@0: * Chris@0: * @throws \InvalidArgumentException When unsupported option is provided Chris@0: */ Chris@0: public function setOptions(array $options) Chris@0: { Chris@17: $this->options = [ Chris@0: 'cache_dir' => null, Chris@0: 'debug' => false, Chris@0: 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', Chris@0: 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', Chris@0: 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', Chris@0: 'generator_cache_class' => 'ProjectUrlGenerator', Chris@0: 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', Chris@0: 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', Chris@0: 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', Chris@0: 'matcher_cache_class' => 'ProjectUrlMatcher', Chris@0: 'resource_type' => null, Chris@0: 'strict_requirements' => true, Chris@17: ]; Chris@0: Chris@0: // check option names and live merge, if errors are encountered Exception will be thrown Chris@17: $invalid = []; Chris@0: foreach ($options as $key => $value) { Chris@18: if (\array_key_exists($key, $this->options)) { Chris@0: $this->options[$key] = $value; Chris@0: } else { Chris@0: $invalid[] = $key; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($invalid) { Chris@0: throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets an option. Chris@0: * Chris@0: * @param string $key The key Chris@0: * @param mixed $value The value Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: public function setOption($key, $value) Chris@0: { Chris@18: if (!\array_key_exists($key, $this->options)) { Chris@0: throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); Chris@0: } Chris@0: Chris@0: $this->options[$key] = $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets an option value. Chris@0: * Chris@0: * @param string $key The key Chris@0: * Chris@0: * @return mixed The value Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: public function getOption($key) Chris@0: { Chris@18: if (!\array_key_exists($key, $this->options)) { Chris@0: throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); Chris@0: } Chris@0: Chris@0: return $this->options[$key]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getRouteCollection() Chris@0: { Chris@0: if (null === $this->collection) { Chris@0: $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); Chris@0: } Chris@0: Chris@0: return $this->collection; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setContext(RequestContext $context) Chris@0: { Chris@0: $this->context = $context; Chris@0: Chris@0: if (null !== $this->matcher) { Chris@0: $this->getMatcher()->setContext($context); Chris@0: } Chris@0: if (null !== $this->generator) { Chris@0: $this->getGenerator()->setContext($context); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getContext() Chris@0: { Chris@0: return $this->context; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the ConfigCache factory to use. Chris@0: */ Chris@0: public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) Chris@0: { Chris@0: $this->configCacheFactory = $configCacheFactory; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@17: public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) Chris@0: { Chris@0: return $this->getGenerator()->generate($name, $parameters, $referenceType); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function match($pathinfo) Chris@0: { Chris@0: return $this->getMatcher()->match($pathinfo); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function matchRequest(Request $request) Chris@0: { Chris@0: $matcher = $this->getMatcher(); Chris@0: if (!$matcher instanceof RequestMatcherInterface) { Chris@0: // fallback to the default UrlMatcherInterface Chris@0: return $matcher->match($request->getPathInfo()); Chris@0: } Chris@0: Chris@0: return $matcher->matchRequest($request); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the UrlMatcher instance associated with this Router. Chris@0: * Chris@0: * @return UrlMatcherInterface A UrlMatcherInterface instance Chris@0: */ Chris@0: public function getMatcher() Chris@0: { Chris@0: if (null !== $this->matcher) { Chris@0: return $this->matcher; Chris@0: } Chris@0: Chris@0: if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { Chris@0: $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); Chris@0: if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { Chris@0: foreach ($this->expressionLanguageProviders as $provider) { Chris@0: $this->matcher->addExpressionLanguageProvider($provider); Chris@0: } Chris@0: } Chris@0: Chris@0: return $this->matcher; Chris@0: } Chris@0: Chris@0: $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php', Chris@0: function (ConfigCacheInterface $cache) { Chris@0: $dumper = $this->getMatcherDumperInstance(); Chris@0: if (method_exists($dumper, 'addExpressionLanguageProvider')) { Chris@0: foreach ($this->expressionLanguageProviders as $provider) { Chris@0: $dumper->addExpressionLanguageProvider($provider); Chris@0: } Chris@0: } Chris@0: Chris@17: $options = [ Chris@0: 'class' => $this->options['matcher_cache_class'], Chris@0: 'base_class' => $this->options['matcher_base_class'], Chris@17: ]; Chris@0: Chris@0: $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); Chris@0: } Chris@0: ); Chris@0: Chris@14: if (!class_exists($this->options['matcher_cache_class'], false)) { Chris@14: require_once $cache->getPath(); Chris@14: } Chris@0: Chris@0: return $this->matcher = new $this->options['matcher_cache_class']($this->context); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the UrlGenerator instance associated with this Router. Chris@0: * Chris@0: * @return UrlGeneratorInterface A UrlGeneratorInterface instance Chris@0: */ Chris@0: public function getGenerator() Chris@0: { Chris@0: if (null !== $this->generator) { Chris@0: return $this->generator; Chris@0: } Chris@0: Chris@0: if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { Chris@0: $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); Chris@0: } else { Chris@0: $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php', Chris@0: function (ConfigCacheInterface $cache) { Chris@0: $dumper = $this->getGeneratorDumperInstance(); Chris@0: Chris@17: $options = [ Chris@0: 'class' => $this->options['generator_cache_class'], Chris@0: 'base_class' => $this->options['generator_base_class'], Chris@17: ]; Chris@0: Chris@0: $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); Chris@0: } Chris@0: ); Chris@0: Chris@14: if (!class_exists($this->options['generator_cache_class'], false)) { Chris@14: require_once $cache->getPath(); Chris@14: } Chris@0: Chris@0: $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger); Chris@0: } Chris@0: Chris@0: if ($this->generator instanceof ConfigurableRequirementsInterface) { Chris@0: $this->generator->setStrictRequirements($this->options['strict_requirements']); Chris@0: } Chris@0: Chris@0: return $this->generator; Chris@0: } Chris@0: Chris@0: public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) Chris@0: { Chris@0: $this->expressionLanguageProviders[] = $provider; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return GeneratorDumperInterface Chris@0: */ Chris@0: protected function getGeneratorDumperInstance() Chris@0: { Chris@0: return new $this->options['generator_dumper_class']($this->getRouteCollection()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return MatcherDumperInterface Chris@0: */ Chris@0: protected function getMatcherDumperInstance() Chris@0: { Chris@0: return new $this->options['matcher_dumper_class']($this->getRouteCollection()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides the ConfigCache factory implementation, falling back to a Chris@0: * default implementation if necessary. Chris@0: * Chris@17: * @return ConfigCacheFactoryInterface Chris@0: */ Chris@0: private function getConfigCacheFactory() Chris@0: { Chris@0: if (null === $this->configCacheFactory) { Chris@0: $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); Chris@0: } Chris@0: Chris@0: return $this->configCacheFactory; Chris@0: } Chris@0: }