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