<?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\NestedMatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; use Symfony\Cmf\Component\Routing\RouteProviderInterface; /** * A more flexible approach to matching. The route collection to match against * can be dynamically determined based on the request and users can inject * their own filters or use a custom final matching strategy. * * The nested matcher splits matching into three configurable steps: * * 1) Get potential matches from a RouteProviderInterface * 2) Apply any RouteFilterInterface to reduce the route collection * 3) Have FinalMatcherInterface select the best match of the remaining routes * * @author Larry Garfield * @author David Buchmann */ class NestedMatcher implements RequestMatcherInterface { /** * The route provider responsible for the first-pass match. * * @var RouteProviderInterface */ protected $routeProvider; /** * The final matcher. * * @var FinalMatcherInterface */ protected $finalMatcher; /** * An array of RouteFilterInterface objects. * * @var RouteFilterInterface[] */ protected $filters = array(); /** * Array of RouteFilterInterface objects, sorted. * * @var RouteFilterInterface[] */ protected $sortedFilters = array(); /** * Constructs a new NestedMatcher. * * @param RouteProviderInterface $provider The route provider this matcher * should use * @param FinalMatcherInterface $final The Final Matcher to match the * routes */ public function __construct( RouteProviderInterface $provider = null, FinalMatcherInterface $final = null ) { if (null !== $provider) { $this->setRouteProvider($provider); } if (null !== $final) { $this->setFinalMatcher($final); } } /** * Sets the route provider for the matching plan. * * @param RouteProviderInterface $provider A source of routes. * * @return NestedMatcher this object to have a fluent interface */ public function setRouteProvider(RouteProviderInterface $provider) { $this->routeProvider = $provider; return $this; } /** * Adds a partial matcher to the matching plan. * * Partial matchers will be run in the order in which they are added. * * @param RouteFilterInterface $filter * @param int $priority (optional) The priority of the * filter. Higher number filters will * be used first. Defaults to 0. * * @return NestedMatcher this object to have a fluent interface */ public function addRouteFilter(RouteFilterInterface $filter, $priority = 0) { if (empty($this->filters[$priority])) { $this->filters[$priority] = array(); } $this->filters[$priority][] = $filter; $this->sortedFilters = array(); return $this; } /** * Sets the final matcher for the matching plan. * * @param FinalMatcherInterface $final The final matcher that will have to * pick the route that will be used. * * @return NestedMatcher this object to have a fluent interface */ public function setFinalMatcher(FinalMatcherInterface $final) { $this->finalMatcher = $final; return $this; } /** * {@inheritdoc} */ public function matchRequest(Request $request) { $collection = $this->routeProvider->getRouteCollectionForRequest($request); if (!count($collection)) { throw new ResourceNotFoundException(); } // Route filters are expected to throw an exception themselves if they // end up filtering the list down to 0. foreach ($this->getRouteFilters() as $filter) { $collection = $filter->filter($collection, $request); } $attributes = $this->finalMatcher->finalMatch($collection, $request); return $attributes; } /** * Sorts the filters and flattens them. * * @return RouteFilterInterface[] the filters ordered by priority */ public function getRouteFilters() { if (empty($this->sortedFilters)) { $this->sortedFilters = $this->sortFilters(); } return $this->sortedFilters; } /** * Sort filters by priority. * * The highest priority number is the highest priority (reverse sorting). * * @return RouteFilterInterface[] the sorted filters */ protected function sortFilters() { $sortedFilters = array(); krsort($this->filters); foreach ($this->filters as $filters) { $sortedFilters = array_merge($sortedFilters, $filters); } return $sortedFilters; } }