--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony CMF package.
+ *
+ * (c) 2011-2015 Symfony CMF
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Cmf\Component\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Cmf\Component\Routing\Event\Events;
+use Symfony\Cmf\Component\Routing\Event\RouterMatchEvent;
+use Symfony\Cmf\Component\Routing\Event\RouterGenerateEvent;
+
+/**
+ * A flexible router accepting matcher and generator through injection and
+ * using the RouteEnhancer concept to generate additional data on the routes.
+ *
+ * @author Larry Garfield
+ * @author David Buchmann
+ */
+class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
+{
+ /**
+ * @var RequestMatcherInterface|UrlMatcherInterface
+ */
+ protected $matcher;
+
+ /**
+ * @var UrlGeneratorInterface
+ */
+ protected $generator;
+
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ * @var RouteEnhancerInterface[]
+ */
+ protected $enhancers = array();
+
+ /**
+ * Cached sorted list of enhancers.
+ *
+ * @var RouteEnhancerInterface[]
+ */
+ protected $sortedEnhancers = array();
+
+ /**
+ * The regexp pattern that needs to be matched before a dynamic lookup is
+ * made.
+ *
+ * @var string
+ */
+ protected $uriFilterRegexp;
+
+ /**
+ * @var RequestContext
+ */
+ protected $context;
+
+ /**
+ * @var RouteCollection
+ */
+ private $routeCollection;
+
+ /**
+ * @param RequestContext $context
+ * @param RequestMatcherInterface|UrlMatcherInterface $matcher
+ * @param UrlGeneratorInterface $generator
+ * @param string $uriFilterRegexp
+ * @param EventDispatcherInterface|null $eventDispatcher
+ * @param RouteProviderInterface $provider
+ */
+ public function __construct(RequestContext $context,
+ $matcher,
+ UrlGeneratorInterface $generator,
+ $uriFilterRegexp = '',
+ EventDispatcherInterface $eventDispatcher = null,
+ RouteProviderInterface $provider = null
+ ) {
+ $this->context = $context;
+ if (!$matcher instanceof RequestMatcherInterface && !$matcher instanceof UrlMatcherInterface) {
+ throw new \InvalidArgumentException('Matcher must implement either Symfony\Component\Routing\Matcher\RequestMatcherInterface or Symfony\Component\Routing\Matcher\UrlMatcherInterface');
+ }
+ $this->matcher = $matcher;
+ $this->generator = $generator;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->uriFilterRegexp = $uriFilterRegexp;
+ $this->provider = $provider;
+
+ $this->generator->setContext($context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRouteCollection()
+ {
+ if (!$this->routeCollection instanceof RouteCollection) {
+ $this->routeCollection = $this->provider
+ ? new LazyRouteCollection($this->provider) : new RouteCollection();
+ }
+
+ return $this->routeCollection;
+ }
+
+ /**
+ * @return RequestMatcherInterface|UrlMatcherInterface
+ */
+ public function getMatcher()
+ {
+ /* we may not set the context in DynamicRouter::setContext as this
+ * would lead to symfony cache warmup problems.
+ * a request matcher does not need the request context separately as it
+ * can get it from the request.
+ */
+ if ($this->matcher instanceof RequestContextAwareInterface) {
+ $this->matcher->setContext($this->getContext());
+ }
+
+ return $this->matcher;
+ }
+
+ /**
+ * @return UrlGeneratorInterface
+ */
+ public function getGenerator()
+ {
+ $this->generator->setContext($this->getContext());
+
+ return $this->generator;
+ }
+
+ /**
+ * Generates a URL from the given parameters.
+ *
+ * If the generator is not able to generate the url, it must throw the
+ * RouteNotFoundException as documented below.
+ *
+ * @param string|Route $name The name of the route or the Route instance
+ * @param mixed $parameters An array of parameters
+ * @param bool|string $referenceType The type of reference to be generated (one of the constants in UrlGeneratorInterface)
+ *
+ * @return string The generated URL
+ *
+ * @throws RouteNotFoundException if route doesn't exist
+ *
+ * @api
+ */
+ public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
+ {
+ if ($this->eventDispatcher) {
+ $event = new RouterGenerateEvent($name, $parameters, $referenceType);
+ $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_GENERATE, $event);
+ $name = $event->getRoute();
+ $parameters = $event->getParameters();
+ $referenceType = $event->getReferenceType();
+ }
+
+ return $this->getGenerator()->generate($name, $parameters, $referenceType);
+ }
+
+ /**
+ * Delegate to our generator.
+ *
+ * {@inheritdoc}
+ */
+ public function supports($name)
+ {
+ if ($this->generator instanceof VersatileGeneratorInterface) {
+ return $this->generator->supports($name);
+ }
+
+ return is_string($name);
+ }
+
+ /**
+ * Tries to match a URL path with a set of routes.
+ *
+ * If the matcher can not find information, it must throw one of the
+ * exceptions documented below.
+ *
+ * @param string $pathinfo The path info to be parsed (raw format, i.e. not
+ * urldecoded)
+ *
+ * @return array An array of parameters
+ *
+ * @throws ResourceNotFoundException If the resource could not be found
+ * @throws MethodNotAllowedException If the resource was found but the
+ * request method is not allowed
+ *
+ * @deprecated Use matchRequest exclusively to avoid problems. This method will be removed in version 2.0
+ *
+ * @api
+ */
+ public function match($pathinfo)
+ {
+ @trigger_error(__METHOD__.'() is deprecated since version 1.3 and will be removed in 2.0. Use matchRequest() instead.', E_USER_DEPRECATED);
+
+ $request = Request::create($pathinfo);
+ if ($this->eventDispatcher) {
+ $event = new RouterMatchEvent();
+ $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
+ }
+
+ if (!empty($this->uriFilterRegexp) && !preg_match($this->uriFilterRegexp, $pathinfo)) {
+ throw new ResourceNotFoundException("$pathinfo does not match the '{$this->uriFilterRegexp}' pattern");
+ }
+
+ $matcher = $this->getMatcher();
+ if (!$matcher instanceof UrlMatcherInterface) {
+ throw new \InvalidArgumentException('Wrong matcher type, you need to call matchRequest');
+ }
+
+ $defaults = $matcher->match($pathinfo);
+
+ return $this->applyRouteEnhancers($defaults, $request);
+ }
+
+ /**
+ * Tries to match a request with a set of routes and returns the array of
+ * information for that route.
+ *
+ * If the matcher can not find information, it must throw one of the
+ * exceptions documented below.
+ *
+ * @param Request $request The request to match
+ *
+ * @return array An array of parameters
+ *
+ * @throws ResourceNotFoundException If no matching resource could be found
+ * @throws MethodNotAllowedException If a matching resource was found but
+ * the request method is not allowed
+ */
+ public function matchRequest(Request $request)
+ {
+ if ($this->eventDispatcher) {
+ $event = new RouterMatchEvent($request);
+ $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
+ }
+
+ if (!empty($this->uriFilterRegexp)
+ && !preg_match($this->uriFilterRegexp, $request->getPathInfo())
+ ) {
+ throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
+ }
+
+ $matcher = $this->getMatcher();
+ if ($matcher instanceof UrlMatcherInterface) {
+ $defaults = $matcher->match($request->getPathInfo());
+ } else {
+ $defaults = $matcher->matchRequest($request);
+ }
+
+ return $this->applyRouteEnhancers($defaults, $request);
+ }
+
+ /**
+ * Apply the route enhancers to the defaults, according to priorities.
+ *
+ * @param array $defaults
+ * @param Request $request
+ *
+ * @return array
+ */
+ protected function applyRouteEnhancers($defaults, Request $request)
+ {
+ foreach ($this->getRouteEnhancers() as $enhancer) {
+ $defaults = $enhancer->enhance($defaults, $request);
+ }
+
+ return $defaults;
+ }
+
+ /**
+ * Add route enhancers to the router to let them generate information on
+ * matched routes.
+ *
+ * The order of the enhancers is determined by the priority, the higher the
+ * value, the earlier the enhancer is run.
+ *
+ * @param RouteEnhancerInterface $enhancer
+ * @param int $priority
+ */
+ public function addRouteEnhancer(RouteEnhancerInterface $enhancer, $priority = 0)
+ {
+ if (empty($this->enhancers[$priority])) {
+ $this->enhancers[$priority] = array();
+ }
+
+ $this->enhancers[$priority][] = $enhancer;
+ $this->sortedEnhancers = array();
+
+ return $this;
+ }
+
+ /**
+ * Sorts the enhancers and flattens them.
+ *
+ * @return RouteEnhancerInterface[] the enhancers ordered by priority
+ */
+ public function getRouteEnhancers()
+ {
+ if (empty($this->sortedEnhancers)) {
+ $this->sortedEnhancers = $this->sortRouteEnhancers();
+ }
+
+ return $this->sortedEnhancers;
+ }
+
+ /**
+ * Sort enhancers by priority.
+ *
+ * The highest priority number is the highest priority (reverse sorting).
+ *
+ * @return RouteEnhancerInterface[] the sorted enhancers
+ */
+ protected function sortRouteEnhancers()
+ {
+ $sortedEnhancers = array();
+ krsort($this->enhancers);
+
+ foreach ($this->enhancers as $enhancers) {
+ $sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
+ }
+
+ return $sortedEnhancers;
+ }
+
+ /**
+ * Sets the request context.
+ *
+ * @param RequestContext $context The context
+ *
+ * @api
+ */
+ public function setContext(RequestContext $context)
+ {
+ $this->context = $context;
+ }
+
+ /**
+ * Gets the request context.
+ *
+ * @return RequestContext The context
+ *
+ * @api
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Forwards to the generator.
+ */
+ public function getRouteDebugMessage($name, array $parameters = array())
+ {
+ if ($this->generator instanceof VersatileGeneratorInterface) {
+ return $this->generator->getRouteDebugMessage($name, $parameters);
+ }
+
+ return "Route '$name' not found";
+ }
+}