UrlMatcher.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Routing\Matcher;
  11. use Symfony\Component\Routing\Exception\MethodNotAllowedException;
  12. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  13. use Symfony\Component\Routing\RouteCollection;
  14. use Symfony\Component\Routing\RequestContext;
  15. use Symfony\Component\Routing\Route;
  16. /**
  17. * UrlMatcher matches URL based on a set of routes.
  18. *
  19. * @author Fabien Potencier <fabien@symfony.com>
  20. *
  21. * @api
  22. */
  23. class UrlMatcher implements UrlMatcherInterface
  24. {
  25. const REQUIREMENT_MATCH = 0;
  26. const REQUIREMENT_MISMATCH = 1;
  27. const ROUTE_MATCH = 2;
  28. /**
  29. * @var RequestContext
  30. */
  31. protected $context;
  32. /**
  33. * @var array
  34. */
  35. protected $allow = array();
  36. /**
  37. * @var RouteCollection
  38. */
  39. protected $routes;
  40. /**
  41. * Constructor.
  42. *
  43. * @param RouteCollection $routes A RouteCollection instance
  44. * @param RequestContext $context The context
  45. *
  46. * @api
  47. */
  48. public function __construct(RouteCollection $routes, RequestContext $context)
  49. {
  50. $this->routes = $routes;
  51. $this->context = $context;
  52. }
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function setContext(RequestContext $context)
  57. {
  58. $this->context = $context;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public function getContext()
  64. {
  65. return $this->context;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function match($pathinfo)
  71. {
  72. $this->allow = array();
  73. if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) {
  74. return $ret;
  75. }
  76. throw 0 < count($this->allow)
  77. ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow)))
  78. : new ResourceNotFoundException();
  79. }
  80. /**
  81. * Tries to match a URL with a set of routes.
  82. *
  83. * @param string $pathinfo The path info to be parsed
  84. * @param RouteCollection $routes The set of routes
  85. *
  86. * @return array An array of parameters
  87. *
  88. * @throws ResourceNotFoundException If the resource could not be found
  89. * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
  90. */
  91. protected function matchCollection($pathinfo, RouteCollection $routes)
  92. {
  93. foreach ($routes as $name => $route) {
  94. $compiledRoute = $route->compile();
  95. // check the static prefix of the URL first. Only use the more expensive preg_match when it matches
  96. if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) {
  97. continue;
  98. }
  99. if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
  100. continue;
  101. }
  102. $hostMatches = array();
  103. if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
  104. continue;
  105. }
  106. // check HTTP method requirement
  107. if ($req = $route->getRequirement('_method')) {
  108. // HEAD and GET are equivalent as per RFC
  109. if ('HEAD' === $method = $this->context->getMethod()) {
  110. $method = 'GET';
  111. }
  112. if (!in_array($method, $req = explode('|', strtoupper($req)))) {
  113. $this->allow = array_merge($this->allow, $req);
  114. continue;
  115. }
  116. }
  117. $status = $this->handleRouteRequirements($pathinfo, $name, $route);
  118. if (self::ROUTE_MATCH === $status[0]) {
  119. return $status[1];
  120. }
  121. if (self::REQUIREMENT_MISMATCH === $status[0]) {
  122. continue;
  123. }
  124. return $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
  125. }
  126. }
  127. /**
  128. * Returns an array of values to use as request attributes.
  129. *
  130. * As this method requires the Route object, it is not available
  131. * in matchers that do not have access to the matched Route instance
  132. * (like the PHP and Apache matcher dumpers).
  133. *
  134. * @param Route $route The route we are matching against
  135. * @param string $name The name of the route
  136. * @param array $attributes An array of attributes from the matcher
  137. *
  138. * @return array An array of parameters
  139. */
  140. protected function getAttributes(Route $route, $name, array $attributes)
  141. {
  142. $attributes['_route'] = $name;
  143. return $this->mergeDefaults($attributes, $route->getDefaults());
  144. }
  145. /**
  146. * Handles specific route requirements.
  147. *
  148. * @param string $pathinfo The path
  149. * @param string $name The route name
  150. * @param Route $route The route
  151. *
  152. * @return array The first element represents the status, the second contains additional information
  153. */
  154. protected function handleRouteRequirements($pathinfo, $name, Route $route)
  155. {
  156. // check HTTP scheme requirement
  157. $scheme = $route->getRequirement('_scheme');
  158. $status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH;
  159. return array($status, null);
  160. }
  161. /**
  162. * Get merged default parameters.
  163. *
  164. * @param array $params The parameters
  165. * @param array $defaults The defaults
  166. *
  167. * @return array Merged default parameters
  168. */
  169. protected function mergeDefaults($params, $defaults)
  170. {
  171. foreach ($params as $key => $value) {
  172. if (!is_int($key)) {
  173. $defaults[$key] = $value;
  174. }
  175. }
  176. return $defaults;
  177. }
  178. }