annotate vendor/symfony/routing/Matcher/UrlMatcher.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 1fec387a4317
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\Routing\Matcher;
Chris@0 13
Chris@0 14 use Symfony\Component\Routing\Exception\MethodNotAllowedException;
Chris@0 15 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
Chris@0 16 use Symfony\Component\Routing\RouteCollection;
Chris@0 17 use Symfony\Component\Routing\RequestContext;
Chris@0 18 use Symfony\Component\Routing\Route;
Chris@0 19 use Symfony\Component\HttpFoundation\Request;
Chris@0 20 use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
Chris@0 21 use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
Chris@0 22
Chris@0 23 /**
Chris@0 24 * UrlMatcher matches URL based on a set of routes.
Chris@0 25 *
Chris@0 26 * @author Fabien Potencier <fabien@symfony.com>
Chris@0 27 */
Chris@0 28 class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
Chris@0 29 {
Chris@0 30 const REQUIREMENT_MATCH = 0;
Chris@0 31 const REQUIREMENT_MISMATCH = 1;
Chris@0 32 const ROUTE_MATCH = 2;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * @var RequestContext
Chris@0 36 */
Chris@0 37 protected $context;
Chris@0 38
Chris@0 39 /**
Chris@0 40 * @var array
Chris@0 41 */
Chris@0 42 protected $allow = array();
Chris@0 43
Chris@0 44 /**
Chris@0 45 * @var RouteCollection
Chris@0 46 */
Chris@0 47 protected $routes;
Chris@0 48
Chris@0 49 protected $request;
Chris@0 50 protected $expressionLanguage;
Chris@0 51
Chris@0 52 /**
Chris@0 53 * @var ExpressionFunctionProviderInterface[]
Chris@0 54 */
Chris@0 55 protected $expressionLanguageProviders = array();
Chris@0 56
Chris@0 57 /**
Chris@0 58 * Constructor.
Chris@0 59 *
Chris@0 60 * @param RouteCollection $routes A RouteCollection instance
Chris@0 61 * @param RequestContext $context The context
Chris@0 62 */
Chris@0 63 public function __construct(RouteCollection $routes, RequestContext $context)
Chris@0 64 {
Chris@0 65 $this->routes = $routes;
Chris@0 66 $this->context = $context;
Chris@0 67 }
Chris@0 68
Chris@0 69 /**
Chris@0 70 * {@inheritdoc}
Chris@0 71 */
Chris@0 72 public function setContext(RequestContext $context)
Chris@0 73 {
Chris@0 74 $this->context = $context;
Chris@0 75 }
Chris@0 76
Chris@0 77 /**
Chris@0 78 * {@inheritdoc}
Chris@0 79 */
Chris@0 80 public function getContext()
Chris@0 81 {
Chris@0 82 return $this->context;
Chris@0 83 }
Chris@0 84
Chris@0 85 /**
Chris@0 86 * {@inheritdoc}
Chris@0 87 */
Chris@0 88 public function match($pathinfo)
Chris@0 89 {
Chris@0 90 $this->allow = array();
Chris@0 91
Chris@0 92 if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
Chris@0 93 return $ret;
Chris@0 94 }
Chris@0 95
Chris@0 96 throw 0 < count($this->allow)
Chris@0 97 ? new MethodNotAllowedException(array_unique($this->allow))
Chris@0 98 : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 */
Chris@0 104 public function matchRequest(Request $request)
Chris@0 105 {
Chris@0 106 $this->request = $request;
Chris@0 107
Chris@0 108 $ret = $this->match($request->getPathInfo());
Chris@0 109
Chris@0 110 $this->request = null;
Chris@0 111
Chris@0 112 return $ret;
Chris@0 113 }
Chris@0 114
Chris@0 115 public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
Chris@0 116 {
Chris@0 117 $this->expressionLanguageProviders[] = $provider;
Chris@0 118 }
Chris@0 119
Chris@0 120 /**
Chris@0 121 * Tries to match a URL with a set of routes.
Chris@0 122 *
Chris@0 123 * @param string $pathinfo The path info to be parsed
Chris@0 124 * @param RouteCollection $routes The set of routes
Chris@0 125 *
Chris@0 126 * @return array An array of parameters
Chris@0 127 *
Chris@0 128 * @throws ResourceNotFoundException If the resource could not be found
Chris@0 129 * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
Chris@0 130 */
Chris@0 131 protected function matchCollection($pathinfo, RouteCollection $routes)
Chris@0 132 {
Chris@0 133 foreach ($routes as $name => $route) {
Chris@0 134 $compiledRoute = $route->compile();
Chris@0 135
Chris@0 136 // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
Chris@0 137 if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) {
Chris@0 138 continue;
Chris@0 139 }
Chris@0 140
Chris@0 141 if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
Chris@0 142 continue;
Chris@0 143 }
Chris@0 144
Chris@0 145 $hostMatches = array();
Chris@0 146 if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
Chris@0 147 continue;
Chris@0 148 }
Chris@0 149
Chris@0 150 // check HTTP method requirement
Chris@0 151 if ($requiredMethods = $route->getMethods()) {
Chris@0 152 // HEAD and GET are equivalent as per RFC
Chris@0 153 if ('HEAD' === $method = $this->context->getMethod()) {
Chris@0 154 $method = 'GET';
Chris@0 155 }
Chris@0 156
Chris@0 157 if (!in_array($method, $requiredMethods)) {
Chris@0 158 $this->allow = array_merge($this->allow, $requiredMethods);
Chris@0 159
Chris@0 160 continue;
Chris@0 161 }
Chris@0 162 }
Chris@0 163
Chris@0 164 $status = $this->handleRouteRequirements($pathinfo, $name, $route);
Chris@0 165
Chris@0 166 if (self::ROUTE_MATCH === $status[0]) {
Chris@0 167 return $status[1];
Chris@0 168 }
Chris@0 169
Chris@0 170 if (self::REQUIREMENT_MISMATCH === $status[0]) {
Chris@0 171 continue;
Chris@0 172 }
Chris@0 173
Chris@0 174 return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
Chris@0 175 }
Chris@0 176 }
Chris@0 177
Chris@0 178 /**
Chris@0 179 * Returns an array of values to use as request attributes.
Chris@0 180 *
Chris@0 181 * As this method requires the Route object, it is not available
Chris@0 182 * in matchers that do not have access to the matched Route instance
Chris@0 183 * (like the PHP and Apache matcher dumpers).
Chris@0 184 *
Chris@0 185 * @param Route $route The route we are matching against
Chris@0 186 * @param string $name The name of the route
Chris@0 187 * @param array $attributes An array of attributes from the matcher
Chris@0 188 *
Chris@0 189 * @return array An array of parameters
Chris@0 190 */
Chris@0 191 protected function getAttributes(Route $route, $name, array $attributes)
Chris@0 192 {
Chris@0 193 $attributes['_route'] = $name;
Chris@0 194
Chris@0 195 return $this->mergeDefaults($attributes, $route->getDefaults());
Chris@0 196 }
Chris@0 197
Chris@0 198 /**
Chris@0 199 * Handles specific route requirements.
Chris@0 200 *
Chris@0 201 * @param string $pathinfo The path
Chris@0 202 * @param string $name The route name
Chris@0 203 * @param Route $route The route
Chris@0 204 *
Chris@0 205 * @return array The first element represents the status, the second contains additional information
Chris@0 206 */
Chris@0 207 protected function handleRouteRequirements($pathinfo, $name, Route $route)
Chris@0 208 {
Chris@0 209 // expression condition
Chris@12 210 if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) {
Chris@0 211 return array(self::REQUIREMENT_MISMATCH, null);
Chris@0 212 }
Chris@0 213
Chris@0 214 // check HTTP scheme requirement
Chris@0 215 $scheme = $this->context->getScheme();
Chris@0 216 $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
Chris@0 217
Chris@0 218 return array($status, null);
Chris@0 219 }
Chris@0 220
Chris@0 221 /**
Chris@0 222 * Get merged default parameters.
Chris@0 223 *
Chris@0 224 * @param array $params The parameters
Chris@0 225 * @param array $defaults The defaults
Chris@0 226 *
Chris@0 227 * @return array Merged default parameters
Chris@0 228 */
Chris@0 229 protected function mergeDefaults($params, $defaults)
Chris@0 230 {
Chris@0 231 foreach ($params as $key => $value) {
Chris@0 232 if (!is_int($key)) {
Chris@0 233 $defaults[$key] = $value;
Chris@0 234 }
Chris@0 235 }
Chris@0 236
Chris@0 237 return $defaults;
Chris@0 238 }
Chris@0 239
Chris@0 240 protected function getExpressionLanguage()
Chris@0 241 {
Chris@0 242 if (null === $this->expressionLanguage) {
Chris@0 243 if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
Chris@0 244 throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
Chris@0 245 }
Chris@0 246 $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
Chris@0 247 }
Chris@0 248
Chris@0 249 return $this->expressionLanguage;
Chris@0 250 }
Chris@12 251
Chris@12 252 /**
Chris@12 253 * @internal
Chris@12 254 */
Chris@12 255 protected function createRequest($pathinfo)
Chris@12 256 {
Chris@12 257 if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
Chris@12 258 return null;
Chris@12 259 }
Chris@12 260
Chris@12 261 return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array(
Chris@12 262 'SCRIPT_FILENAME' => $this->context->getBaseUrl(),
Chris@12 263 'SCRIPT_NAME' => $this->context->getBaseUrl(),
Chris@12 264 ));
Chris@12 265 }
Chris@0 266 }