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\HttpFoundation\Request;
|
Chris@0
|
15 use Symfony\Component\Routing\Exception\ExceptionInterface;
|
Chris@0
|
16 use Symfony\Component\Routing\Route;
|
Chris@0
|
17 use Symfony\Component\Routing\RouteCollection;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * TraceableUrlMatcher helps debug path info matching by tracing the match.
|
Chris@0
|
21 *
|
Chris@0
|
22 * @author Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
23 */
|
Chris@0
|
24 class TraceableUrlMatcher extends UrlMatcher
|
Chris@0
|
25 {
|
Chris@0
|
26 const ROUTE_DOES_NOT_MATCH = 0;
|
Chris@0
|
27 const ROUTE_ALMOST_MATCHES = 1;
|
Chris@0
|
28 const ROUTE_MATCHES = 2;
|
Chris@0
|
29
|
Chris@0
|
30 protected $traces;
|
Chris@0
|
31
|
Chris@0
|
32 public function getTraces($pathinfo)
|
Chris@0
|
33 {
|
Chris@17
|
34 $this->traces = [];
|
Chris@0
|
35
|
Chris@0
|
36 try {
|
Chris@0
|
37 $this->match($pathinfo);
|
Chris@0
|
38 } catch (ExceptionInterface $e) {
|
Chris@0
|
39 }
|
Chris@0
|
40
|
Chris@0
|
41 return $this->traces;
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 public function getTracesForRequest(Request $request)
|
Chris@0
|
45 {
|
Chris@0
|
46 $this->request = $request;
|
Chris@0
|
47 $traces = $this->getTraces($request->getPathInfo());
|
Chris@0
|
48 $this->request = null;
|
Chris@0
|
49
|
Chris@0
|
50 return $traces;
|
Chris@0
|
51 }
|
Chris@0
|
52
|
Chris@0
|
53 protected function matchCollection($pathinfo, RouteCollection $routes)
|
Chris@0
|
54 {
|
Chris@0
|
55 foreach ($routes as $name => $route) {
|
Chris@0
|
56 $compiledRoute = $route->compile();
|
Chris@0
|
57
|
Chris@0
|
58 if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) {
|
Chris@0
|
59 // does it match without any requirements?
|
Chris@17
|
60 $r = new Route($route->getPath(), $route->getDefaults(), [], $route->getOptions());
|
Chris@0
|
61 $cr = $r->compile();
|
Chris@0
|
62 if (!preg_match($cr->getRegex(), $pathinfo)) {
|
Chris@0
|
63 $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route);
|
Chris@0
|
64
|
Chris@0
|
65 continue;
|
Chris@0
|
66 }
|
Chris@0
|
67
|
Chris@0
|
68 foreach ($route->getRequirements() as $n => $regex) {
|
Chris@17
|
69 $r = new Route($route->getPath(), $route->getDefaults(), [$n => $regex], $route->getOptions());
|
Chris@0
|
70 $cr = $r->compile();
|
Chris@0
|
71
|
Chris@17
|
72 if (\in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) {
|
Chris@0
|
73 $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route);
|
Chris@0
|
74
|
Chris@0
|
75 continue 2;
|
Chris@0
|
76 }
|
Chris@0
|
77 }
|
Chris@0
|
78
|
Chris@0
|
79 continue;
|
Chris@0
|
80 }
|
Chris@0
|
81
|
Chris@0
|
82 // check host requirement
|
Chris@17
|
83 $hostMatches = [];
|
Chris@0
|
84 if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {
|
Chris@0
|
85 $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route);
|
Chris@0
|
86
|
Chris@0
|
87 continue;
|
Chris@0
|
88 }
|
Chris@0
|
89
|
Chris@0
|
90 // check HTTP method requirement
|
Chris@0
|
91 if ($requiredMethods = $route->getMethods()) {
|
Chris@0
|
92 // HEAD and GET are equivalent as per RFC
|
Chris@0
|
93 if ('HEAD' === $method = $this->context->getMethod()) {
|
Chris@0
|
94 $method = 'GET';
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@17
|
97 if (!\in_array($method, $requiredMethods)) {
|
Chris@0
|
98 $this->allow = array_merge($this->allow, $requiredMethods);
|
Chris@0
|
99
|
Chris@0
|
100 $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
|
101
|
Chris@0
|
102 continue;
|
Chris@0
|
103 }
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 // check condition
|
Chris@0
|
107 if ($condition = $route->getCondition()) {
|
Chris@17
|
108 if (!$this->getExpressionLanguage()->evaluate($condition, ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) {
|
Chris@0
|
109 $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route);
|
Chris@0
|
110
|
Chris@0
|
111 continue;
|
Chris@0
|
112 }
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 // check HTTP scheme requirement
|
Chris@0
|
116 if ($requiredSchemes = $route->getSchemes()) {
|
Chris@0
|
117 $scheme = $this->context->getScheme();
|
Chris@0
|
118
|
Chris@0
|
119 if (!$route->hasScheme($scheme)) {
|
Chris@0
|
120 $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
|
121
|
Chris@0
|
122 return true;
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
|
Chris@0
|
127
|
Chris@0
|
128 return true;
|
Chris@0
|
129 }
|
Chris@0
|
130 }
|
Chris@0
|
131
|
Chris@0
|
132 private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null)
|
Chris@0
|
133 {
|
Chris@17
|
134 $this->traces[] = [
|
Chris@0
|
135 'log' => $log,
|
Chris@0
|
136 'name' => $name,
|
Chris@0
|
137 'level' => $level,
|
Chris@0
|
138 'path' => null !== $route ? $route->getPath() : null,
|
Chris@17
|
139 ];
|
Chris@0
|
140 }
|
Chris@0
|
141 }
|