Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Routing\Matcher; Chris@0: Chris@0: use Symfony\Component\HttpFoundation\Request; Chris@0: use Symfony\Component\Routing\Exception\ExceptionInterface; Chris@0: use Symfony\Component\Routing\Route; Chris@0: use Symfony\Component\Routing\RouteCollection; Chris@0: Chris@0: /** Chris@0: * TraceableUrlMatcher helps debug path info matching by tracing the match. Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class TraceableUrlMatcher extends UrlMatcher Chris@0: { Chris@0: const ROUTE_DOES_NOT_MATCH = 0; Chris@0: const ROUTE_ALMOST_MATCHES = 1; Chris@0: const ROUTE_MATCHES = 2; Chris@0: Chris@0: protected $traces; Chris@0: Chris@0: public function getTraces($pathinfo) Chris@0: { Chris@17: $this->traces = []; Chris@0: Chris@0: try { Chris@0: $this->match($pathinfo); Chris@0: } catch (ExceptionInterface $e) { Chris@0: } Chris@0: Chris@0: return $this->traces; Chris@0: } Chris@0: Chris@0: public function getTracesForRequest(Request $request) Chris@0: { Chris@0: $this->request = $request; Chris@0: $traces = $this->getTraces($request->getPathInfo()); Chris@0: $this->request = null; Chris@0: Chris@0: return $traces; Chris@0: } Chris@0: Chris@0: protected function matchCollection($pathinfo, RouteCollection $routes) Chris@0: { Chris@0: foreach ($routes as $name => $route) { Chris@0: $compiledRoute = $route->compile(); Chris@0: Chris@0: if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { Chris@0: // does it match without any requirements? Chris@17: $r = new Route($route->getPath(), $route->getDefaults(), [], $route->getOptions()); Chris@0: $cr = $r->compile(); Chris@0: if (!preg_match($cr->getRegex(), $pathinfo)) { Chris@0: $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: foreach ($route->getRequirements() as $n => $regex) { Chris@17: $r = new Route($route->getPath(), $route->getDefaults(), [$n => $regex], $route->getOptions()); Chris@0: $cr = $r->compile(); Chris@0: Chris@17: if (\in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { Chris@0: $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); Chris@0: Chris@0: continue 2; Chris@0: } Chris@0: } Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: // check host requirement Chris@17: $hostMatches = []; Chris@0: if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { Chris@0: $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: // check HTTP method requirement Chris@0: if ($requiredMethods = $route->getMethods()) { Chris@0: // HEAD and GET are equivalent as per RFC Chris@0: if ('HEAD' === $method = $this->context->getMethod()) { Chris@0: $method = 'GET'; Chris@0: } Chris@0: Chris@17: if (!\in_array($method, $requiredMethods)) { Chris@0: $this->allow = array_merge($this->allow, $requiredMethods); Chris@0: Chris@0: $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); Chris@0: Chris@0: continue; Chris@0: } Chris@0: } Chris@0: Chris@0: // check condition Chris@0: if ($condition = $route->getCondition()) { Chris@17: if (!$this->getExpressionLanguage()->evaluate($condition, ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) { Chris@0: $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); Chris@0: Chris@0: continue; Chris@0: } Chris@0: } Chris@0: Chris@0: // check HTTP scheme requirement Chris@0: if ($requiredSchemes = $route->getSchemes()) { Chris@0: $scheme = $this->context->getScheme(); Chris@0: Chris@0: if (!$route->hasScheme($scheme)) { Chris@0: $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route); Chris@0: Chris@0: return true; Chris@0: } Chris@0: } Chris@0: Chris@0: $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); Chris@0: Chris@0: return true; Chris@0: } Chris@0: } Chris@0: Chris@0: private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) Chris@0: { Chris@17: $this->traces[] = [ Chris@0: 'log' => $log, Chris@0: 'name' => $name, Chris@0: 'level' => $level, Chris@0: 'path' => null !== $route ? $route->getPath() : null, Chris@17: ]; Chris@0: } Chris@0: }