DynamicRouter.php 10.5 KB
Newer Older
1 2
<?php

3 4 5 6 7 8 9 10 11 12
/*
 * This file is part of the Symfony CMF package.
 *
 * (c) 2011-2013 Symfony CMF
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


13 14 15 16 17 18 19 20 21 22
namespace Symfony\Cmf\Component\Routing;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
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;
23
use Symfony\Component\Routing\Exception\RouteNotFoundException;
24
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
25
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
26
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
27 28 29
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Cmf\Component\Routing\Event\Events;
use Symfony\Cmf\Component\Routing\Event\RouterMatchEvent;
30 31 32 33 34

/**
 * A flexible router accepting matcher and generator through injection and
 * using the RouteEnhancer concept to generate additional data on the routes.
 *
35
 * @author Larry Garfield
36 37 38 39 40 41 42 43 44 45 46 47 48 49
 * @author David Buchmann
 */
class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
{
    /**
     * @var RequestMatcherInterface|UrlMatcherInterface
     */
    protected $matcher;

    /**
     * @var UrlGeneratorInterface
     */
    protected $generator;

50 51 52 53 54
    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    /**
     * @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;

    /**
     * @param RequestContext                              $context
     * @param RequestMatcherInterface|UrlMatcherInterface $matcher
     * @param UrlGeneratorInterface                       $generator
     * @param string                                      $uriFilterRegexp
84
     * @param EventDispatcherInterface|null               $eventDispatcher
85
     */
86 87 88 89 90 91
    public function __construct(RequestContext $context,
                                $matcher,
                                UrlGeneratorInterface $generator,
                                $uriFilterRegexp = '',
                                EventDispatcherInterface $eventDispatcher = null
    ) {
92 93 94 95 96 97
        $this->context = $context;
        if (! $matcher instanceof RequestMatcherInterface && ! $matcher instanceof UrlMatcherInterface) {
            throw new \InvalidArgumentException('Invalid $matcher');
        }
        $this->matcher = $matcher;
        $this->generator = $generator;
98
        $this->eventDispatcher = $eventDispatcher;
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
        $this->uriFilterRegexp = $uriFilterRegexp;

        $this->generator->setContext($context);
    }

    /**
     * Not implemented.
     */
    public function getRouteCollection()
    {
        return new RouteCollection();
    }

    /**
     * @return RequestMatcherInterface|UrlMatcherInterface
     */
    public function getMatcher()
    {
117 118 119 120 121
        /* 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.
         */
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
        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.
     *
142 143
     * If the generator is not able to generate the url, it must throw the
     * RouteNotFoundException as documented below.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
     *
     * @param string  $name       The name of the route
     * @param mixed   $parameters An array of parameters
     * @param Boolean $absolute   Whether to generate an absolute URL
     *
     * @return string The generated URL
     *
     * @throws RouteNotFoundException if route doesn't exist
     *
     * @api
     */
    public function generate($name, $parameters = array(), $absolute = false)
    {
        return $this->getGenerator()->generate($name, $parameters, $absolute);
    }

    /**
161
     * Delegate to our generator
162 163 164 165 166
     *
     * {@inheritDoc}
     */
    public function supports($name)
    {
167 168 169 170
        if ($this->generator instanceof VersatileGeneratorInterface) {
            return $this->generator->supports($name);
        }

171
        return is_string($name);
172 173 174 175 176 177 178 179
    }

    /**
     * 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.
     *
180 181
     * @param string $pathinfo The path info to be parsed (raw format, i.e. not
     *      urldecoded)
182 183 184 185
     *
     * @return array An array of parameters
     *
     * @throws ResourceNotFoundException If the resource could not be found
186 187
     * @throws MethodNotAllowedException If the resource was found but the
     *      request method is not allowed
188 189 190 191 192
     *
     * @api
     */
    public function match($pathinfo)
    {
193 194 195 196 197 198
        $request = Request::create($pathinfo);
        if ($this->eventDispatcher) {
            $event = new RouterMatchEvent();
            $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
        }

199 200 201 202 203 204 205 206 207 208 209
        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);

210
        return $this->applyRouteEnhancers($defaults, $request);
211 212 213 214 215 216 217 218 219 220 221 222 223 224
    }

    /**
     * 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
225 226
     * @throws MethodNotAllowedException If a matching resource was found but
     *      the request method is not allowed
227 228 229
     */
    public function matchRequest(Request $request)
    {
230 231 232 233 234
        if ($this->eventDispatcher) {
            $event = new RouterMatchEvent($request);
            $this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
        }

235 236 237
        if (! empty($this->uriFilterRegexp)
            && ! preg_match($this->uriFilterRegexp, $request->getPathInfo())
        ) {
238 239 240 241 242
            throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
        }

        $matcher = $this->getMatcher();
        if ($matcher instanceof UrlMatcherInterface) {
243 244 245
            $defaults = $matcher->match($request->getPathInfo());
        } else {
            $defaults = $matcher->matchRequest($request);
246 247 248 249 250 251 252 253
        }

        return $this->applyRouteEnhancers($defaults, $request);
    }

    /**
     * Apply the route enhancers to the defaults, according to priorities
     *
254
     * @param array   $defaults
255
     * @param Request $request
256
     *
257 258 259 260 261 262 263
     * @return array
     */
    protected function applyRouteEnhancers($defaults, Request $request)
    {
        foreach ($this->getRouteEnhancers() as $enhancer) {
            $defaults = $enhancer->enhance($defaults, $request);
        }
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
        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;
    }
346 347 348 349 350 351 352 353 354 355 356 357 358 359

    /**
     * {@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";
    }
360
}