Passed
Branch dev (709f37)
by Alex
03:14
created
src/Collectors/ControllerCollectorTrait.php 1 patch
Indentation   +229 added lines, -229 removed lines patch added patch discarded remove patch
@@ -26,235 +26,235 @@
 block discarded – undo
26 26
 trait ControllerCollectorTrait
27 27
 {
28 28
 
29
-    /**
30
-     * @return Parser
31
-     */
32
-
33
-    abstract public function getParser();
34
-
35
-    /**
36
-     * @param string $method
37
-     * @param string $pattern
38
-     * @param callable $action
39
-     *
40
-     * @return Group
41
-     */
42
-
43
-    abstract public function set($method, $pattern, $action);
44
-
45
-    /**
46
-     * Define how controller actions names will be joined to form the route pattern.
47
-     *
48
-     * @var string
49
-     */
50
-
51
-    protected $controllerActionJoin = "/";
52
-
53
-    /**
54
-     * Maps all the controller methods that begins with a HTTP method, and maps the rest of
55
-     * name as a path. The path will be the method name with slashes before every camelcased 
56
-     * word and without the HTTP method prefix, and the controller name will be used to prefix
57
-     * the route pattern. e.g. ArticlesController::getCreate will generate a route to: GET articles/create
58
-     *
59
-     * @param string $controller The controller name
60
-     * @param string $prefix
61
-     *
62
-     * @throws \ReflectionException
63
-     * @return Group
64
-     */
65
-
66
-    public function controller($controller, $prefix = null)
67
-    {
68
-        $controller = new ReflectionClass($controller);
69
-        $prefix     = $prefix === null ? $this->getControllerPrefix($controller) : $prefix;
70
-        $methods    = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
71
-        return $this->collectControllerRoutes($controller, $methods, "/$prefix/");
72
-    }
73
-
74
-    /**
75
-     * Maps several controllers at same time.
76
-     *
77
-     * @param string[] $controllers Controllers name.
78
-     * @throws \ReflectionException
79
-     * @return Group
80
-     */
81
-
82
-    public function controllers(array $controllers)
83
-    {
84
-        $group = new Group;
85
-        foreach ($controllers as $controller)
86
-            $group->set($this->controller($controller));
87
-        return $group;
88
-    }
89
-
90
-    /**
91
-     * Alias for Collector::controller but maps a controller without using the controller name as prefix.
92
-     *
93
-     * @param string $controller The controller name
94
-     * @throws \ReflectionException
95
-     * @return Group
96
-     */
97
-
98
-    public function controllerWithoutPrefix($controller)
99
-    {
100
-        $controller = new ReflectionClass($controller);
101
-        $methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
102
-        return $this->collectControllerRoutes($controller, $methods, "/");
103
-    }
104
-
105
-    /**
106
-     * Alias for Collector::controllers but maps a controller without using the controller name as prefix.
107
-     *
108
-     * @param string[] $controllers
109
-     * @throws \ReflectionException
110
-     * @return Group
111
-     */
112
-
113
-    public function controllersWithoutPrefix(array $controllers)
114
-    {
115
-        $group = new Group;
116
-        foreach ($controllers as $controller)
117
-            $group->set($this->controllerWithoutPrefix($controller));
118
-        return $group;
119
-    }
120
-
121
-    /**
122
-     * @param ReflectionClass $controller
123
-     * @param ReflectionMethod[] $methods
124
-     * @param string $prefix
125
-     *
126
-     * @return Group
127
-     */
128
-
129
-    protected function collectControllerRoutes(ReflectionClass $controller, array $methods, $prefix)
130
-    {
131
-        $group = new Group;
132
-        $controllerDefaultStrategy = $this->getAnnotatedStrategy($controller);
133
-
134
-        foreach ($methods as $method) {
135
-            $name = preg_split("~(?=[A-Z])~", $method->name);
136
-            $http = $name[0];
137
-            unset($name[0]);
29
+	/**
30
+	 * @return Parser
31
+	 */
32
+
33
+	abstract public function getParser();
34
+
35
+	/**
36
+	 * @param string $method
37
+	 * @param string $pattern
38
+	 * @param callable $action
39
+	 *
40
+	 * @return Group
41
+	 */
42
+
43
+	abstract public function set($method, $pattern, $action);
44
+
45
+	/**
46
+	 * Define how controller actions names will be joined to form the route pattern.
47
+	 *
48
+	 * @var string
49
+	 */
50
+
51
+	protected $controllerActionJoin = "/";
52
+
53
+	/**
54
+	 * Maps all the controller methods that begins with a HTTP method, and maps the rest of
55
+	 * name as a path. The path will be the method name with slashes before every camelcased 
56
+	 * word and without the HTTP method prefix, and the controller name will be used to prefix
57
+	 * the route pattern. e.g. ArticlesController::getCreate will generate a route to: GET articles/create
58
+	 *
59
+	 * @param string $controller The controller name
60
+	 * @param string $prefix
61
+	 *
62
+	 * @throws \ReflectionException
63
+	 * @return Group
64
+	 */
65
+
66
+	public function controller($controller, $prefix = null)
67
+	{
68
+		$controller = new ReflectionClass($controller);
69
+		$prefix     = $prefix === null ? $this->getControllerPrefix($controller) : $prefix;
70
+		$methods    = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
71
+		return $this->collectControllerRoutes($controller, $methods, "/$prefix/");
72
+	}
73
+
74
+	/**
75
+	 * Maps several controllers at same time.
76
+	 *
77
+	 * @param string[] $controllers Controllers name.
78
+	 * @throws \ReflectionException
79
+	 * @return Group
80
+	 */
81
+
82
+	public function controllers(array $controllers)
83
+	{
84
+		$group = new Group;
85
+		foreach ($controllers as $controller)
86
+			$group->set($this->controller($controller));
87
+		return $group;
88
+	}
89
+
90
+	/**
91
+	 * Alias for Collector::controller but maps a controller without using the controller name as prefix.
92
+	 *
93
+	 * @param string $controller The controller name
94
+	 * @throws \ReflectionException
95
+	 * @return Group
96
+	 */
97
+
98
+	public function controllerWithoutPrefix($controller)
99
+	{
100
+		$controller = new ReflectionClass($controller);
101
+		$methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
102
+		return $this->collectControllerRoutes($controller, $methods, "/");
103
+	}
104
+
105
+	/**
106
+	 * Alias for Collector::controllers but maps a controller without using the controller name as prefix.
107
+	 *
108
+	 * @param string[] $controllers
109
+	 * @throws \ReflectionException
110
+	 * @return Group
111
+	 */
112
+
113
+	public function controllersWithoutPrefix(array $controllers)
114
+	{
115
+		$group = new Group;
116
+		foreach ($controllers as $controller)
117
+			$group->set($this->controllerWithoutPrefix($controller));
118
+		return $group;
119
+	}
120
+
121
+	/**
122
+	 * @param ReflectionClass $controller
123
+	 * @param ReflectionMethod[] $methods
124
+	 * @param string $prefix
125
+	 *
126
+	 * @return Group
127
+	 */
128
+
129
+	protected function collectControllerRoutes(ReflectionClass $controller, array $methods, $prefix)
130
+	{
131
+		$group = new Group;
132
+		$controllerDefaultStrategy = $this->getAnnotatedStrategy($controller);
133
+
134
+		foreach ($methods as $method) {
135
+			$name = preg_split("~(?=[A-Z])~", $method->name);
136
+			$http = $name[0];
137
+			unset($name[0]);
138 138
  
139
-            if (strpos(Collector::HTTP_METHODS, $http) !== false) {
140
-                $action   = $prefix . strtolower(implode($this->controllerActionJoin, $name));
141
-                $dynamic  = $this->getMethodConstraints($method);
142
-                $strategy = $this->getAnnotatedStrategy($method);
143
-
144
-                $route = $this->set($http, "$action$dynamic", [$controller->name, $method->name]);
145
-
146
-                if ($strategy !== null) {
147
-                       $route->setStrategy($strategy);
148
-                } else $route->setStrategy($controllerDefaultStrategy);
149
-
150
-                $group->set($route);
151
-            }
152
-        }
153
-
154
-        return $group;
155
-    }
156
-
157
-    /**
158
-     * @param ReflectionClass $controller
159
-     *
160
-     * @return string
161
-     */
162
-
163
-    protected function getControllerPrefix(ReflectionClass $controller)
164
-    {
165
-        preg_match("~\@prefix\s([a-zA-Z\\\_]+)~i", (string) $controller->getDocComment(), $prefix);
166
-        return isset($prefix[1]) ? $prefix[1] : str_replace("controller", "", strtolower($controller->getShortName()));
167
-    }
168
-
169
-    /**
170
-     * @param \ReflectionMethod
171
-     * @return string
172
-     */
173
-
174
-    protected function getMethodConstraints(ReflectionMethod $method)
175
-    {
176
-        $beginPath = "";
177
-        $endPath = "";
178
-
179
-        if ($parameters = $method->getParameters()) {
180
-            $types = $this->getParamsConstraint($method);
181
-
182
-            foreach ($parameters as $parameter) {
183
-                if ($parameter->isOptional()) {
184
-                    $beginPath .= "[";
185
-                    $endPath .= "]";
186
-                }
187
-
188
-                $beginPath .= $this->getPathConstraint($parameter, $types);
189
-            }
190
-        }
191
-
192
-        return $beginPath . $endPath;
193
-    }
194
-
195
-    /**
196
-     * @param ReflectionParameter $parameter
197
-     * @param string[] $types
198
-     * @return string
199
-     */
200
-
201
-    protected function getPathConstraint(ReflectionParameter $parameter, $types)
202
-    {
203
-        $name = $parameter->name;
204
-        $path = "/{" . $name;
205
-        return isset($types[$name]) ? "$path:{$types[$name]}}" : "$path}";
206
-    }
207
-
208
-    /**
209
-     * @param ReflectionMethod $method
210
-     * @return string[]
211
-     */
212
-
213
-    protected function getParamsConstraint(ReflectionMethod $method)
214
-    {
215
-        $params = [];
216
-        preg_match_all("~\@param\s(" . implode("|", array_keys($this->getParser()->getWildcards())) . "|\(.+\))\s\\$([a-zA-Z0-1_]+)~i",
217
-            $method->getDocComment(), $types, PREG_SET_ORDER);
218
-
219
-        foreach ((array) $types as $type) {
220
-            // if a pattern is defined on Match take it otherwise take the param type by PHPDoc.
221
-            $params[$type[2]] = isset($type[4]) ? $type[4] : $type[1];
222
-        }
223
-
224
-        return $params;
225
-    }
226
-
227
-    /**
228
-     * @param ReflectionClass|ReflectionMethod $reflector
229
-     * @return string|null
230
-     */
231
-
232
-    protected function getAnnotatedStrategy($reflector)
233
-    {
234
-        preg_match("~\@strategy\s([a-zA-Z\\\_]+)~i", (string) $reflector->getDocComment(), $strategy);
235
-        return isset($strategy[1]) ? $strategy[1] : null;
236
-    }
237
-
238
-    /**
239
-     * Define how controller actions names will be joined to form the route pattern.
240
-     * Defaults to "/" so actions like "getMyAction" will be "/my/action". If changed to
241
-     * "-" the new pattern will be "/my-action".
242
-     *
243
-     * @param string $join
244
-     */
245
-
246
-    public function setControllerActionJoin($join)
247
-    {
248
-        $this->controllerActionJoin = $join;
249
-    }
250
-
251
-    /**
252
-     * @return string
253
-     */
254
-
255
-    public function getControllerActionJoin()
256
-    {
257
-        return $this->controllerActionJoin;
258
-    }
139
+			if (strpos(Collector::HTTP_METHODS, $http) !== false) {
140
+				$action   = $prefix . strtolower(implode($this->controllerActionJoin, $name));
141
+				$dynamic  = $this->getMethodConstraints($method);
142
+				$strategy = $this->getAnnotatedStrategy($method);
143
+
144
+				$route = $this->set($http, "$action$dynamic", [$controller->name, $method->name]);
145
+
146
+				if ($strategy !== null) {
147
+					   $route->setStrategy($strategy);
148
+				} else $route->setStrategy($controllerDefaultStrategy);
149
+
150
+				$group->set($route);
151
+			}
152
+		}
153
+
154
+		return $group;
155
+	}
156
+
157
+	/**
158
+	 * @param ReflectionClass $controller
159
+	 *
160
+	 * @return string
161
+	 */
162
+
163
+	protected function getControllerPrefix(ReflectionClass $controller)
164
+	{
165
+		preg_match("~\@prefix\s([a-zA-Z\\\_]+)~i", (string) $controller->getDocComment(), $prefix);
166
+		return isset($prefix[1]) ? $prefix[1] : str_replace("controller", "", strtolower($controller->getShortName()));
167
+	}
168
+
169
+	/**
170
+	 * @param \ReflectionMethod
171
+	 * @return string
172
+	 */
173
+
174
+	protected function getMethodConstraints(ReflectionMethod $method)
175
+	{
176
+		$beginPath = "";
177
+		$endPath = "";
178
+
179
+		if ($parameters = $method->getParameters()) {
180
+			$types = $this->getParamsConstraint($method);
181
+
182
+			foreach ($parameters as $parameter) {
183
+				if ($parameter->isOptional()) {
184
+					$beginPath .= "[";
185
+					$endPath .= "]";
186
+				}
187
+
188
+				$beginPath .= $this->getPathConstraint($parameter, $types);
189
+			}
190
+		}
191
+
192
+		return $beginPath . $endPath;
193
+	}
194
+
195
+	/**
196
+	 * @param ReflectionParameter $parameter
197
+	 * @param string[] $types
198
+	 * @return string
199
+	 */
200
+
201
+	protected function getPathConstraint(ReflectionParameter $parameter, $types)
202
+	{
203
+		$name = $parameter->name;
204
+		$path = "/{" . $name;
205
+		return isset($types[$name]) ? "$path:{$types[$name]}}" : "$path}";
206
+	}
207
+
208
+	/**
209
+	 * @param ReflectionMethod $method
210
+	 * @return string[]
211
+	 */
212
+
213
+	protected function getParamsConstraint(ReflectionMethod $method)
214
+	{
215
+		$params = [];
216
+		preg_match_all("~\@param\s(" . implode("|", array_keys($this->getParser()->getWildcards())) . "|\(.+\))\s\\$([a-zA-Z0-1_]+)~i",
217
+			$method->getDocComment(), $types, PREG_SET_ORDER);
218
+
219
+		foreach ((array) $types as $type) {
220
+			// if a pattern is defined on Match take it otherwise take the param type by PHPDoc.
221
+			$params[$type[2]] = isset($type[4]) ? $type[4] : $type[1];
222
+		}
223
+
224
+		return $params;
225
+	}
226
+
227
+	/**
228
+	 * @param ReflectionClass|ReflectionMethod $reflector
229
+	 * @return string|null
230
+	 */
231
+
232
+	protected function getAnnotatedStrategy($reflector)
233
+	{
234
+		preg_match("~\@strategy\s([a-zA-Z\\\_]+)~i", (string) $reflector->getDocComment(), $strategy);
235
+		return isset($strategy[1]) ? $strategy[1] : null;
236
+	}
237
+
238
+	/**
239
+	 * Define how controller actions names will be joined to form the route pattern.
240
+	 * Defaults to "/" so actions like "getMyAction" will be "/my/action". If changed to
241
+	 * "-" the new pattern will be "/my-action".
242
+	 *
243
+	 * @param string $join
244
+	 */
245
+
246
+	public function setControllerActionJoin($join)
247
+	{
248
+		$this->controllerActionJoin = $join;
249
+	}
250
+
251
+	/**
252
+	 * @return string
253
+	 */
254
+
255
+	public function getControllerActionJoin()
256
+	{
257
+		return $this->controllerActionJoin;
258
+	}
259 259
 
260 260
 }
Please login to merge, or discard this patch.
src/Matcher.php 1 patch
Indentation   +273 added lines, -273 removed lines patch added patch discarded remove patch
@@ -23,279 +23,279 @@
 block discarded – undo
23 23
 class Matcher
24 24
 {
25 25
 
26
-    /**
27
-     * @var Collector
28
-     */
29
-
30
-    protected $collector;
31
-
32
-    /**
33
-     * @var Parser $parser
34
-     */
35
-
36
-    protected $parser;
37
-
38
-    /**
39
-     * Define a basepath to all routes.
40
-     *
41
-     * @var string
42
-     */
43
-
44
-    protected $basepath = "";
45
-
46
-    /**
47
-     * Construct the route dispatcher.
48
-     *
49
-     * @param Collector $collector
50
-     * @param string $basepath Define a Path prefix that must be excluded on matches.
51
-     */
52
-
53
-    public function __construct(Collector $collector, $basepath = "")
54
-    {
55
-        $this->collector = $collector;
56
-        $this->basepath  = $basepath;
57
-    }
58
-
59
-    /**
60
-     * Find a route that matches the given arguments.
61
-     * 
62
-     * @param string $httpMethod
63
-     * @param string $path
64
-     *
65
-     * @throws NotFoundException
66
-     * @throws MethodNotAllowedException
67
-     *
68
-     * @return Route
69
-     */
70
-
71
-    public function match($httpMethod, $path)
72
-    {
73
-        $path = $this->parsePath($path);
74
-
75
-        if (($route = $this->collector->findStaticRoute($httpMethod, $path)) ||
76
-            ($route = $this->matchDynamicRoute($httpMethod, $path))) {
77
-             $route->setMatcher($this);
78
-
79
-            return $route;
80
-        }
81
-
82
-        $this->matchSimilarRoute($httpMethod, $path);
83
-    }
84
-
85
-    /**
86
-     * Find and return the request dynamic route based on the compiled data and Path.
87
-     *
88
-     * @param string $httpMethod
89
-     * @param string $path
90
-     *
91
-     * @return Route|false If the request match an array with the action and parameters will
92
-     *                     be returned otherwise a false will.
93
-     */
94
-
95
-    protected function matchDynamicRoute($httpMethod, $path)
96
-    {
97
-        if ($routes = $this->collector->findDynamicRoutes($httpMethod, $path)) {
98
-            // cache the parser reference
99
-            $this->parser = $this->collector->getParser();
100
-            // chunk routes for smaller regex groups using the Sturges' Formula
101
-            foreach (array_chunk($routes, round(1 + 3.3 * log(count($routes))), true) as $chunk) {
102
-                array_map([$this, "buildRoute"], $chunk);
103
-                list($pattern, $map) = $this->buildGroup($chunk);
104
-
105
-                if (!preg_match($pattern, $path, $matches)) {
106
-                    continue;
107
-                }
108
-
109
-                /** @var Route $route */
110
-                $route = $map[count($matches)];
111
-                unset($matches[0]);
112
-
113
-                $route->setParams(array_combine($route->getParams(), array_filter($matches)));
114
-
115
-                return $route;
116
-            }
117
-        }
118
-
119
-        return false;
120
-    }
121
-
122
-    /**
123
-     * Parse the dynamic segments of the pattern and replace then for
124
-     * corresponding regex.
125
-     *
126
-     * @param Route $route
127
-     * @return Route
128
-     */
129
-
130
-    protected function buildRoute(Route $route)
131
-    {
132
-        if ($route->getBlock()) {
133
-            return $route;
134
-        }
135
-
136
-        list($pattern, $params) = $this->parsePlaceholders($route->getPattern());
137
-        return $route->setPatternWithoutReset($pattern)->setParams($params)->setBlock(true);
138
-    }
139
-
140
-    /**
141
-     * Group several dynamic routes patterns into one big regex and maps
142
-     * the routes to the pattern positions in the big regex.
143
-     *
144
-     * @param Route[] $routes
145
-     * @return array
146
-     */
147
-
148
-    protected function buildGroup(array $routes)
149
-    {
150
-        $groupCount = (int) $map = $regex = [];
151
-
152
-        foreach ($routes as $route) {
153
-            $params           = $route->getParams();
154
-            $paramsCount      = count($params);
155
-            $groupCount       = max($groupCount, $paramsCount) + 1;
156
-            $regex[]          = $route->getPattern() . str_repeat("()", $groupCount - $paramsCount - 1);
157
-            $map[$groupCount] = $route;
158
-        }
159
-
160
-        return ["~^(?|" . implode("|", $regex) . ")$~", $map];
161
-    }
162
-
163
-    /**
164
-     * Parse an route pattern seeking for parameters and build the route regex.
165
-     *
166
-     * @param string $pattern
167
-     * @return array 0 => new route regex, 1 => map of parameter names
168
-     */
169
-
170
-    protected function parsePlaceholders($pattern)
171
-    {
172
-        $params = [];
173
-        $parser = $this->parser;
174
-
175
-        preg_match_all("~" . $parser::DYNAMIC_REGEX . "~x", $pattern, $matches, PREG_SET_ORDER);
176
-
177
-        foreach ((array) $matches as $key => $match) {
178
-            $pattern = str_replace($match[0], isset($match[2]) ? "({$match[2]})" : "([^/]+)", $pattern);
179
-            $params[$key] = $match[1];
180
-        }
181
-
182
-        return [$pattern, $params];
183
-    }
184
-
185
-    /**
186
-     * Get only the path of a given url.
187
-     *
188
-     * @param string $path The given URL
189
-     *
190
-     * @throws Exception
191
-     * @return string
192
-     */
193
-
194
-    protected function parsePath($path)
195
-    {
196
-        $path = parse_url(substr(strstr(";" . $path, ";" . $this->basepath), strlen(";" . $this->basepath)), PHP_URL_PATH);
197
-
198
-        if ($path === false) {
199
-            throw new Exception("Seriously malformed URL passed to route matcher.");
200
-        }
201
-
202
-        return $path;
203
-    }
204
-
205
-    /**
206
-     * Generate an HTTP error request with method not allowed or not found.
207
-     *
208
-     * @param string $httpMethod
209
-     * @param string $path
210
-     *
211
-     * @throws NotFoundException
212
-     * @throws MethodNotAllowedException
213
-     */
214
-
215
-    protected function matchSimilarRoute($httpMethod, $path)
216
-    {
217
-        $dm = [];
218
-
219
-        if (($sm = $this->checkStaticRouteInOtherMethods($httpMethod, $path))
220
-                || ($dm = $this->checkDynamicRouteInOtherMethods($httpMethod, $path))) {
221
-            throw new MethodNotAllowedException($httpMethod, $path, array_merge((array) $sm, (array) $dm));
222
-        }
223
-
224
-        throw new NotFoundException;
225
-    }
226
-
227
-    /**
228
-     * Verify if a static route match in another method than the requested.
229
-     *
230
-     * @param string $targetHttpMethod The HTTP method that must not be checked
231
-     * @param string $path              The Path that must be matched.
232
-     *
233
-     * @return array
234
-     */
235
-
236
-    protected function checkStaticRouteInOtherMethods($targetHttpMethod, $path)
237
-    {
238
-        return array_filter($this->getHttpMethodsBut($targetHttpMethod), function ($httpMethod) use ($path) {
239
-            return (bool) $this->collector->findStaticRoute($httpMethod, $path);
240
-        });
241
-    }
242
-
243
-    /**
244
-     * Verify if a dynamic route match in another method than the requested.
245
-     *
246
-     * @param string $targetHttpMethod The HTTP method that must not be checked
247
-     * @param string $path             The Path that must be matched.
248
-     *
249
-     * @return array
250
-     */
251
-
252
-    protected function checkDynamicRouteInOtherMethods($targetHttpMethod, $path)
253
-    {
254
-        return array_filter($this->getHttpMethodsBut($targetHttpMethod), function ($httpMethod) use ($path) {
255
-            return (bool) $this->matchDynamicRoute($httpMethod, $path);
256
-        });
257
-    }
258
-
259
-    /**
260
-     * Strip the given http methods and return all the others.
261
-     *
262
-     * @param string|string[]
263
-     * @return array
264
-     */
265
-
266
-    protected function getHttpMethodsBut($targetHttpMethod)
267
-    {
268
-        return array_diff(explode(" ", Collector::HTTP_METHODS), (array) $targetHttpMethod);
269
-    }
270
-
271
-    /**
272
-     * @return Collector
273
-     */
274
-
275
-    public function getCollector()
276
-    {
277
-        return $this->collector;
278
-    }
279
-
280
-    /**
281
-     * @return string
282
-     */
283
-
284
-    public function getBasePath()
285
-    {
286
-        return $this->basepath;
287
-    }
288
-
289
-    /**
290
-     * Set a new basepath, this will be a prefix that must be excluded in
291
-     * every requested Path.
292
-     *
293
-     * @param string $basepath The new basepath
294
-     */
26
+	/**
27
+	 * @var Collector
28
+	 */
29
+
30
+	protected $collector;
31
+
32
+	/**
33
+	 * @var Parser $parser
34
+	 */
35
+
36
+	protected $parser;
37
+
38
+	/**
39
+	 * Define a basepath to all routes.
40
+	 *
41
+	 * @var string
42
+	 */
43
+
44
+	protected $basepath = "";
45
+
46
+	/**
47
+	 * Construct the route dispatcher.
48
+	 *
49
+	 * @param Collector $collector
50
+	 * @param string $basepath Define a Path prefix that must be excluded on matches.
51
+	 */
52
+
53
+	public function __construct(Collector $collector, $basepath = "")
54
+	{
55
+		$this->collector = $collector;
56
+		$this->basepath  = $basepath;
57
+	}
58
+
59
+	/**
60
+	 * Find a route that matches the given arguments.
61
+	 * 
62
+	 * @param string $httpMethod
63
+	 * @param string $path
64
+	 *
65
+	 * @throws NotFoundException
66
+	 * @throws MethodNotAllowedException
67
+	 *
68
+	 * @return Route
69
+	 */
70
+
71
+	public function match($httpMethod, $path)
72
+	{
73
+		$path = $this->parsePath($path);
74
+
75
+		if (($route = $this->collector->findStaticRoute($httpMethod, $path)) ||
76
+			($route = $this->matchDynamicRoute($httpMethod, $path))) {
77
+			 $route->setMatcher($this);
78
+
79
+			return $route;
80
+		}
81
+
82
+		$this->matchSimilarRoute($httpMethod, $path);
83
+	}
84
+
85
+	/**
86
+	 * Find and return the request dynamic route based on the compiled data and Path.
87
+	 *
88
+	 * @param string $httpMethod
89
+	 * @param string $path
90
+	 *
91
+	 * @return Route|false If the request match an array with the action and parameters will
92
+	 *                     be returned otherwise a false will.
93
+	 */
94
+
95
+	protected function matchDynamicRoute($httpMethod, $path)
96
+	{
97
+		if ($routes = $this->collector->findDynamicRoutes($httpMethod, $path)) {
98
+			// cache the parser reference
99
+			$this->parser = $this->collector->getParser();
100
+			// chunk routes for smaller regex groups using the Sturges' Formula
101
+			foreach (array_chunk($routes, round(1 + 3.3 * log(count($routes))), true) as $chunk) {
102
+				array_map([$this, "buildRoute"], $chunk);
103
+				list($pattern, $map) = $this->buildGroup($chunk);
104
+
105
+				if (!preg_match($pattern, $path, $matches)) {
106
+					continue;
107
+				}
108
+
109
+				/** @var Route $route */
110
+				$route = $map[count($matches)];
111
+				unset($matches[0]);
112
+
113
+				$route->setParams(array_combine($route->getParams(), array_filter($matches)));
114
+
115
+				return $route;
116
+			}
117
+		}
118
+
119
+		return false;
120
+	}
121
+
122
+	/**
123
+	 * Parse the dynamic segments of the pattern and replace then for
124
+	 * corresponding regex.
125
+	 *
126
+	 * @param Route $route
127
+	 * @return Route
128
+	 */
129
+
130
+	protected function buildRoute(Route $route)
131
+	{
132
+		if ($route->getBlock()) {
133
+			return $route;
134
+		}
135
+
136
+		list($pattern, $params) = $this->parsePlaceholders($route->getPattern());
137
+		return $route->setPatternWithoutReset($pattern)->setParams($params)->setBlock(true);
138
+	}
139
+
140
+	/**
141
+	 * Group several dynamic routes patterns into one big regex and maps
142
+	 * the routes to the pattern positions in the big regex.
143
+	 *
144
+	 * @param Route[] $routes
145
+	 * @return array
146
+	 */
147
+
148
+	protected function buildGroup(array $routes)
149
+	{
150
+		$groupCount = (int) $map = $regex = [];
151
+
152
+		foreach ($routes as $route) {
153
+			$params           = $route->getParams();
154
+			$paramsCount      = count($params);
155
+			$groupCount       = max($groupCount, $paramsCount) + 1;
156
+			$regex[]          = $route->getPattern() . str_repeat("()", $groupCount - $paramsCount - 1);
157
+			$map[$groupCount] = $route;
158
+		}
159
+
160
+		return ["~^(?|" . implode("|", $regex) . ")$~", $map];
161
+	}
162
+
163
+	/**
164
+	 * Parse an route pattern seeking for parameters and build the route regex.
165
+	 *
166
+	 * @param string $pattern
167
+	 * @return array 0 => new route regex, 1 => map of parameter names
168
+	 */
169
+
170
+	protected function parsePlaceholders($pattern)
171
+	{
172
+		$params = [];
173
+		$parser = $this->parser;
174
+
175
+		preg_match_all("~" . $parser::DYNAMIC_REGEX . "~x", $pattern, $matches, PREG_SET_ORDER);
176
+
177
+		foreach ((array) $matches as $key => $match) {
178
+			$pattern = str_replace($match[0], isset($match[2]) ? "({$match[2]})" : "([^/]+)", $pattern);
179
+			$params[$key] = $match[1];
180
+		}
181
+
182
+		return [$pattern, $params];
183
+	}
184
+
185
+	/**
186
+	 * Get only the path of a given url.
187
+	 *
188
+	 * @param string $path The given URL
189
+	 *
190
+	 * @throws Exception
191
+	 * @return string
192
+	 */
193
+
194
+	protected function parsePath($path)
195
+	{
196
+		$path = parse_url(substr(strstr(";" . $path, ";" . $this->basepath), strlen(";" . $this->basepath)), PHP_URL_PATH);
197
+
198
+		if ($path === false) {
199
+			throw new Exception("Seriously malformed URL passed to route matcher.");
200
+		}
201
+
202
+		return $path;
203
+	}
204
+
205
+	/**
206
+	 * Generate an HTTP error request with method not allowed or not found.
207
+	 *
208
+	 * @param string $httpMethod
209
+	 * @param string $path
210
+	 *
211
+	 * @throws NotFoundException
212
+	 * @throws MethodNotAllowedException
213
+	 */
214
+
215
+	protected function matchSimilarRoute($httpMethod, $path)
216
+	{
217
+		$dm = [];
218
+
219
+		if (($sm = $this->checkStaticRouteInOtherMethods($httpMethod, $path))
220
+				|| ($dm = $this->checkDynamicRouteInOtherMethods($httpMethod, $path))) {
221
+			throw new MethodNotAllowedException($httpMethod, $path, array_merge((array) $sm, (array) $dm));
222
+		}
223
+
224
+		throw new NotFoundException;
225
+	}
226
+
227
+	/**
228
+	 * Verify if a static route match in another method than the requested.
229
+	 *
230
+	 * @param string $targetHttpMethod The HTTP method that must not be checked
231
+	 * @param string $path              The Path that must be matched.
232
+	 *
233
+	 * @return array
234
+	 */
235
+
236
+	protected function checkStaticRouteInOtherMethods($targetHttpMethod, $path)
237
+	{
238
+		return array_filter($this->getHttpMethodsBut($targetHttpMethod), function ($httpMethod) use ($path) {
239
+			return (bool) $this->collector->findStaticRoute($httpMethod, $path);
240
+		});
241
+	}
242
+
243
+	/**
244
+	 * Verify if a dynamic route match in another method than the requested.
245
+	 *
246
+	 * @param string $targetHttpMethod The HTTP method that must not be checked
247
+	 * @param string $path             The Path that must be matched.
248
+	 *
249
+	 * @return array
250
+	 */
251
+
252
+	protected function checkDynamicRouteInOtherMethods($targetHttpMethod, $path)
253
+	{
254
+		return array_filter($this->getHttpMethodsBut($targetHttpMethod), function ($httpMethod) use ($path) {
255
+			return (bool) $this->matchDynamicRoute($httpMethod, $path);
256
+		});
257
+	}
258
+
259
+	/**
260
+	 * Strip the given http methods and return all the others.
261
+	 *
262
+	 * @param string|string[]
263
+	 * @return array
264
+	 */
265
+
266
+	protected function getHttpMethodsBut($targetHttpMethod)
267
+	{
268
+		return array_diff(explode(" ", Collector::HTTP_METHODS), (array) $targetHttpMethod);
269
+	}
270
+
271
+	/**
272
+	 * @return Collector
273
+	 */
274
+
275
+	public function getCollector()
276
+	{
277
+		return $this->collector;
278
+	}
279
+
280
+	/**
281
+	 * @return string
282
+	 */
283
+
284
+	public function getBasePath()
285
+	{
286
+		return $this->basepath;
287
+	}
288
+
289
+	/**
290
+	 * Set a new basepath, this will be a prefix that must be excluded in
291
+	 * every requested Path.
292
+	 *
293
+	 * @param string $basepath The new basepath
294
+	 */
295 295
     
296
-    public function setBasePath($basepath)
297
-    {
298
-        $this->basepath = $basepath;
299
-    }
296
+	public function setBasePath($basepath)
297
+	{
298
+		$this->basepath = $basepath;
299
+	}
300 300
 
301 301
 }
Please login to merge, or discard this patch.
src/Route.php 1 patch
Indentation   +616 added lines, -616 removed lines patch added patch discarded remove patch
@@ -23,626 +23,626 @@
 block discarded – undo
23 23
 class Route
24 24
 {
25 25
 
26
-    /**
27
-     * @var Collector
28
-     */
26
+	/**
27
+	 * @var Collector
28
+	 */
29 29
 
30
-    protected $collector;
30
+	protected $collector;
31 31
 
32
-    /**
33
-     * @var string
34
-     */
32
+	/**
33
+	 * @var string
34
+	 */
35 35
 
36
-    protected $method;
36
+	protected $method;
37 37
 
38
-    /**
39
-     * @var string
40
-     */
41
-
42
-    protected $pattern;
38
+	/**
39
+	 * @var string
40
+	 */
41
+
42
+	protected $pattern;
43 43
 
44
-    /**
45
-     * @var callable
46
-     */
47
-
48
-    protected $action;
49
-
50
-    /**
51
-     * @var string
52
-     */
53
-
54
-    protected $namespace = "";
55
-
56
-    /**
57
-     * @var string[]
58
-     */
59
-
60
-    protected $params = [];
61
-
62
-    /**
63
-     * Defaults are parameters set by the user, and don't
64
-     * appear on the pattern.
65
-     *
66
-     * @var array
67
-     */
68
-
69
-    protected $defaults = [];
70
-
71
-    /**
72
-     * Metadata can be set to be used on filters, dispatch strategies
73
-     * or anywhere the route object is used.
74
-     *
75
-     * @var array
76
-     */
77
-
78
-    protected $metadata = [];
79
-
80
-    /**
81
-     * @var string|StrategyInterface
82
-     */
83
-
84
-    protected $strategy;
85
-
86
-    /**
87
-     * Blocked routes are dynamic routes selected to pass by the matcher.
88
-     *
89
-     * @var boolean
90
-     */
91
-
92
-    protected $blocked = false;
93
-
94
-    /**
95
-     * The matcher that dispatched this route.
96
-     *
97
-     * @var Matcher $matcher
98
-     */
99
-
100
-    protected $matcher;
101
-
102
-    /**
103
-     * The function used to create controllers from name.
104
-     *
105
-     * @var callable
106
-     */
107
-
108
-    protected $controllerCreationFunction;
109
-
110
-    /**
111
-     * @param Collector $collector
112
-     * @param string $method
113
-     * @param string $pattern
114
-     * @param callable $action
115
-     */
116
-
117
-    public function __construct(Collector $collector, $method, $pattern, $action)
118
-    {
119
-        $this->collector = $collector;
120
-        $this->method    = $method;
121
-        $this->pattern   = $pattern;
122
-        $this->action    = $action;
123
-    }
124
-
125
-    /**
126
-     * Clone this route and set it into the collector.
127
-     *
128
-     * @return Route
129
-     */
130
-
131
-    public function reset()
132
-    {
133
-        return $this->collector->set($this->method, $this->pattern, $this->action)->nth(0)
134
-                               ->setStrategy($this->strategy)->setParams($this->params)
135
-                               ->setDefaults($this->defaults)->setMetadataArray($this->metadata);
136
-    }
137
-
138
-    /**
139
-     * Remove this route from the collector.
140
-     *
141
-     * @return self
142
-     */
143
-
144
-    public function forget()
145
-    {
146
-        $this->collector->forget($this->method, $this->pattern);
147
-        return $this;
148
-    }
149
-
150
-    /**
151
-     * Execute the route action, if no strategy was provided the action
152
-     * will be executed by the call_user_func PHP function.
153
-     *
154
-     * @param callable $container
155
-     * @throws BadRouteException
156
-     * @return mixed
157
-     */
158
-
159
-    public function call(callable $container = null)
160
-    {
161
-        $this->action = $this->buildCallable($this->action, $container);
162
-
163
-        if ($this->strategy === null) {
164
-            return call_user_func_array($this->action, array_merge($this->defaults, $this->params));
165
-        }
166
-
167
-        if (!is_object($this->strategy)) {
168
-            if ($container === null) {
169
-                   $this->strategy = new $this->strategy;
170
-            } else $this->strategy = $container($this->strategy);
171
-        }
172
-
173
-        return $this->callWithStrategy();
174
-    }
175
-
176
-    /**
177
-     * Seek for dynamic content in one callable. This allow to use parameters defined on pattern on callable
178
-     * definition, eg. "get" "/{resource:string+}/{slug:slug+}" "{resource}::find".
179
-     *
180
-     * This will snakecase the resource parameter and deal with as a controller, then call the find method.
181
-     * A request for "/articles/my-first-article" will execute find method of Articles controller with only
182
-     * "my-first-article" as parameter.
183
-     *
184
-     * @param callable $callable
185
-     * @param callable $container
186
-     *
187
-     * @return callable
188
-     */
189
-
190
-    private function buildCallable($callable, $container = null)
191
-    {
192
-        if (is_string($callable) && strpos($callable, "::")) {
193
-            $callable = explode("::", $callable);
194
-        }
195
-
196
-        if (is_array($callable)) {
197
-            if (is_string($callable[0])) {
198
-                   $callable[0] = $this->parseCallableController($callable[0], $container);
199
-            }
200
-
201
-            $callable[1] = $this->parseCallablePlaceholders($callable[1]);
202
-        }
203
-
204
-        return $callable;
205
-    }
206
-
207
-    /**
208
-     * Get the controller object.
209
-     *
210
-     * @param string $controller
211
-     * @param callable $container
212
-     *
213
-     * @return Object
214
-     */
215
-
216
-    private function parseCallableController($controller, $container)
217
-    {
218
-        $controller  = rtrim($this->namespace, "\\") . "\\" . $this->parseCallablePlaceholders($controller);
219
-
220
-        if ($container === null) {
221
-               return new $controller;
222
-        } else return $container($controller);
223
-    }
224
-
225
-    /**
226
-     * Parse and replace dynamic content on route action.
227
-     *
228
-     * @param string $fragment Part of callable
229
-     * @return string
230
-     */
231
-
232
-    private function parseCallablePlaceholders($fragment)
233
-    {
234
-        if (strpos($fragment, "{") !== false) {
235
-            foreach ($this->params as $placeholder => $value) {
236
-                if (strpos($fragment, "{" . $placeholder . "}") !== false) {
237
-                    $fragment = str_replace("{" . $placeholder . "}", ucwords(str_replace("-", " ", $value)), $fragment);
238
-                }
239
-            }
240
-        }
241
-
242
-        return $fragment;
243
-    }
244
-
245
-    /**
246
-     * Execute the route action with the given strategy.
247
-     *
248
-     * @throws BadRouteException
249
-     * @return mixed
250
-     */
251
-
252
-    private function callWithStrategy()
253
-    {
254
-        if ($this->strategy instanceof StrategyInterface) {
255
-            if ($this->strategy instanceof MatcherAwareInterface) {
256
-                $this->strategy->setMatcher($this->matcher);
257
-            }
258
-
259
-            return $this->strategy->call($this);
260
-        }
261
-
262
-        throw new BadRouteException(str_replace("%s", get_class($this->strategy), BadRouteException::BAD_STRATEGY));
263
-    }
264
-
265
-    /**
266
-     * @return Collector
267
-     */
268
-
269
-    public function getCollector()
270
-    {
271
-        return $this->collector;
272
-    }
273
-
274
-    /**
275
-     * @return string
276
-     */
277
-
278
-    public function getMethod()
279
-    {
280
-        return $this->method;
281
-    }
282
-
283
-    /**
284
-     * @return string
285
-     */
286
-
287
-    public function getPattern()
288
-    {
289
-        return $this->pattern;
290
-    }
291
-
292
-    /**
293
-     * @return string[]
294
-     */
295
-
296
-    public function getSegments()
297
-    {
298
-        return explode("/", $this->pattern);
299
-    }
300
-
301
-    /**
302
-     * @return callable
303
-     */
304
-
305
-    public function getAction()
306
-    {
307
-        return $this->action;
308
-    }
309
-
310
-    /**
311
-     * @return string
312
-     */
313
-
314
-    public function getNamespace()
315
-    {
316
-        return $this->namespace;
317
-    }
318
-
319
-    /**
320
-     * @return string[]
321
-     */
322
-
323
-    public function getParams()
324
-    {
325
-        return $this->params;
326
-    }
327
-
328
-    /**
329
-     * @param string $key
330
-     * @return string
331
-     */
332
-
333
-    public function getParam($key)
334
-    {
335
-        return $this->params[$key];
336
-    }
337
-
338
-    /**
339
-     * Return defaults and params merged in one array.
340
-     *
341
-     * @return array
342
-     */
343
-
344
-    public function getMergedParams()
345
-    {
346
-        return array_merge($this->defaults, $this->params);
347
-    }
348
-
349
-    /**
350
-     * @return array
351
-     */
352
-
353
-    public function getDefaults()
354
-    {
355
-        return $this->defaults;
356
-    }
357
-
358
-    /**
359
-     * @param string $key
360
-     * @return mixed
361
-     */
362
-
363
-    public function getDefault($key)
364
-    {
365
-        return $this->defaults[$key];
366
-    }
367
-
368
-    /**
369
-     * @return array
370
-     */
371
-
372
-    public function getMetadataArray()
373
-    {
374
-        return $this->metadata;
375
-    }
376
-
377
-    /**
378
-     * @param string $key
379
-     * @return mixed
380
-     */
381
-
382
-    public function getMetadata($key)
383
-    {
384
-        return $this->metadata[$key];
385
-    }
386
-
387
-    /**
388
-     * @return string|null
389
-     */
390
-
391
-    public function getStrategy()
392
-    {
393
-        if ($this->strategy instanceof StrategyInterface) {
394
-            return get_class($this->strategy);
395
-        }
396
-
397
-        return $this->strategy;
398
-    }
399
-
400
-    /**
401
-     * @return StrategyInterface|string
402
-     */
403
-
404
-    public function getRawStrategy()
405
-    {
406
-        return $this->strategy;
407
-    }
408
-
409
-    /**
410
-     * @return Matcher
411
-     */
412
-
413
-    public function getMatcher()
414
-    {
415
-        return $this->matcher;
416
-    }
417
-
418
-    /**
419
-     * Verify if a Route have already been blocked.
420
-     *
421
-     * @return boolean
422
-     */
423
-
424
-    public function getBlock()
425
-    {
426
-        return $this->blocked;
427
-    }
428
-
429
-    /**
430
-     * Blocking a route indicate that that route have been selected and
431
-     * parsed, now it will be given to the matcher.
432
-     *
433
-     * @param bool $blocked
434
-     * @return self
435
-     */
436
-
437
-    public function setBlock($blocked)
438
-    {
439
-        $this->blocked = $blocked;
440
-        return $this;
441
-    }
442
-
443
-    /**
444
-     * @param string $method
445
-     * @return Route
446
-     */
447
-
448
-    public function setMethod($method)
449
-    {
450
-        $this->forget();
451
-        $this->method = $method;
452
-        return $this->reset();
453
-    }
454
-
455
-    /**
456
-     * @param string $pattern
457
-     * @return Route
458
-     */
459
-
460
-    public function setPattern($pattern)
461
-    {
462
-        $this->forget();
463
-        $this->pattern = $pattern;
464
-        return $this->reset();
465
-    }
466
-
467
-    /**
468
-     * @param string $pattern
469
-     * @return self
470
-     */
471
-
472
-    public function setPatternWithoutReset($pattern)
473
-    {
474
-        $this->pattern = $pattern;
475
-        return $this;
476
-    }
477
-
478
-    /**
479
-     * @param string $action
480
-     * @return self
481
-     */
482
-
483
-    public function setAction($action)
484
-    {
485
-        $this->action = $action;
486
-        return $this;
487
-    }
488
-
489
-    /**
490
-     * @param string $namespace
491
-     * @return self
492
-     */
493
-
494
-    public function setNamespace($namespace)
495
-    {
496
-        $this->namespace = $namespace;
497
-        return $this;
498
-    }
499
-
500
-    /**
501
-     * @param string[] $params
502
-     * @return self
503
-     */
504
-
505
-    public function setParams(array $params)
506
-    {
507
-        $this->params = $params;
508
-        return $this;
509
-    }
510
-
511
-    /**
512
-     * @param string $key
513
-     * @param string $value
514
-     *
515
-     * @return self
516
-     */
517
-
518
-    public function setParam($key, $value)
519
-    {
520
-        $this->params[$key] = $value;
521
-        return $this;
522
-    }
523
-
524
-    /**
525
-     * @param mixed[] $defaults
526
-     * @return self
527
-     */
528
-
529
-    public function setDefaults(array $defaults)
530
-    {
531
-        $this->defaults = $defaults;
532
-        return $this;
533
-    }
534
-
535
-    /**
536
-     * @param string $key
537
-     * @param mixed $value
538
-     *
539
-     * @return self
540
-     */
541
-
542
-    public function setDefault($key, $value)
543
-    {
544
-        $this->defaults[$key] = $value;
545
-        return $this;
546
-    }
547
-
548
-    /**
549
-     * @param mixed[] $metadata
550
-     * @return self
551
-     */
552
-
553
-    public function setMetadataArray(array $metadata)
554
-    {
555
-        $this->metadata = $metadata;
556
-        return $this;
557
-    }
558
-
559
-    /**
560
-     * @param string $key
561
-     * @param mixed $value
562
-     *
563
-     * @return $this
564
-     */
565
-
566
-    public function setMetadata($key, $value)
567
-    {
568
-        $this->metadata[$key] = $value;
569
-        return $this;
570
-    }
571
-
572
-    /**
573
-     * @param null|string|StrategyInterface $strategy
574
-     * @return self
575
-     */
576
-
577
-    public function setStrategy($strategy)
578
-    {
579
-        $this->strategy = $strategy;
580
-        return $this;
581
-    }
582
-
583
-    /**
584
-     * @param Matcher $matcher
585
-     * @return self
586
-     */
587
-
588
-    public function setMatcher(Matcher $matcher)
589
-    {
590
-        $this->matcher = $matcher;
591
-        return $this;
592
-    }
593
-
594
-    /**
595
-     * Set a constraint to a token in the route pattern.
596
-     *
597
-     * @param string $token
598
-     * @param string $regex
599
-     *
600
-     * @return self
601
-     */
602
-
603
-    public function setConstraint($token, $regex)
604
-    {
605
-        $initPos = strpos($this->pattern, "{" . $token);
606
-
607
-        if ($initPos !== false) {
608
-            $endPos = strpos($this->pattern, "}", $initPos);
609
-            $newPattern = substr_replace($this->pattern, "{" . "$token:$regex" . "}", $initPos, $endPos - $initPos + 1);
610
-            $wildcards = $this->collector->getParser()->getWildcardTokens();
611
-            $newPattern = str_replace(array_keys($wildcards), $wildcards, $newPattern);
612
-            $this->setPatternWithoutReset($newPattern);
613
-        }
614
-
615
-        return $this;
616
-    }
617
-
618
-    /**
619
-     * @param string $key
620
-     * @return bool
621
-     */
622
-
623
-    public function hasParam($key)
624
-    {
625
-        return isset($this->params[$key]);
626
-    }
627
-
628
-    /**
629
-     * @param string $key
630
-     * @return bool
631
-     */
632
-
633
-    public function hasDefault($key)
634
-    {
635
-        return isset($this->defaults[$key]);
636
-    }
637
-
638
-    /**
639
-     * @param string $key
640
-     * @return bool
641
-     */
642
-
643
-    public function hasMetadata($key)
644
-    {
645
-        return isset($this->metadata[$key]);
646
-    }
44
+	/**
45
+	 * @var callable
46
+	 */
47
+
48
+	protected $action;
49
+
50
+	/**
51
+	 * @var string
52
+	 */
53
+
54
+	protected $namespace = "";
55
+
56
+	/**
57
+	 * @var string[]
58
+	 */
59
+
60
+	protected $params = [];
61
+
62
+	/**
63
+	 * Defaults are parameters set by the user, and don't
64
+	 * appear on the pattern.
65
+	 *
66
+	 * @var array
67
+	 */
68
+
69
+	protected $defaults = [];
70
+
71
+	/**
72
+	 * Metadata can be set to be used on filters, dispatch strategies
73
+	 * or anywhere the route object is used.
74
+	 *
75
+	 * @var array
76
+	 */
77
+
78
+	protected $metadata = [];
79
+
80
+	/**
81
+	 * @var string|StrategyInterface
82
+	 */
83
+
84
+	protected $strategy;
85
+
86
+	/**
87
+	 * Blocked routes are dynamic routes selected to pass by the matcher.
88
+	 *
89
+	 * @var boolean
90
+	 */
91
+
92
+	protected $blocked = false;
93
+
94
+	/**
95
+	 * The matcher that dispatched this route.
96
+	 *
97
+	 * @var Matcher $matcher
98
+	 */
99
+
100
+	protected $matcher;
101
+
102
+	/**
103
+	 * The function used to create controllers from name.
104
+	 *
105
+	 * @var callable
106
+	 */
107
+
108
+	protected $controllerCreationFunction;
109
+
110
+	/**
111
+	 * @param Collector $collector
112
+	 * @param string $method
113
+	 * @param string $pattern
114
+	 * @param callable $action
115
+	 */
116
+
117
+	public function __construct(Collector $collector, $method, $pattern, $action)
118
+	{
119
+		$this->collector = $collector;
120
+		$this->method    = $method;
121
+		$this->pattern   = $pattern;
122
+		$this->action    = $action;
123
+	}
124
+
125
+	/**
126
+	 * Clone this route and set it into the collector.
127
+	 *
128
+	 * @return Route
129
+	 */
130
+
131
+	public function reset()
132
+	{
133
+		return $this->collector->set($this->method, $this->pattern, $this->action)->nth(0)
134
+							   ->setStrategy($this->strategy)->setParams($this->params)
135
+							   ->setDefaults($this->defaults)->setMetadataArray($this->metadata);
136
+	}
137
+
138
+	/**
139
+	 * Remove this route from the collector.
140
+	 *
141
+	 * @return self
142
+	 */
143
+
144
+	public function forget()
145
+	{
146
+		$this->collector->forget($this->method, $this->pattern);
147
+		return $this;
148
+	}
149
+
150
+	/**
151
+	 * Execute the route action, if no strategy was provided the action
152
+	 * will be executed by the call_user_func PHP function.
153
+	 *
154
+	 * @param callable $container
155
+	 * @throws BadRouteException
156
+	 * @return mixed
157
+	 */
158
+
159
+	public function call(callable $container = null)
160
+	{
161
+		$this->action = $this->buildCallable($this->action, $container);
162
+
163
+		if ($this->strategy === null) {
164
+			return call_user_func_array($this->action, array_merge($this->defaults, $this->params));
165
+		}
166
+
167
+		if (!is_object($this->strategy)) {
168
+			if ($container === null) {
169
+				   $this->strategy = new $this->strategy;
170
+			} else $this->strategy = $container($this->strategy);
171
+		}
172
+
173
+		return $this->callWithStrategy();
174
+	}
175
+
176
+	/**
177
+	 * Seek for dynamic content in one callable. This allow to use parameters defined on pattern on callable
178
+	 * definition, eg. "get" "/{resource:string+}/{slug:slug+}" "{resource}::find".
179
+	 *
180
+	 * This will snakecase the resource parameter and deal with as a controller, then call the find method.
181
+	 * A request for "/articles/my-first-article" will execute find method of Articles controller with only
182
+	 * "my-first-article" as parameter.
183
+	 *
184
+	 * @param callable $callable
185
+	 * @param callable $container
186
+	 *
187
+	 * @return callable
188
+	 */
189
+
190
+	private function buildCallable($callable, $container = null)
191
+	{
192
+		if (is_string($callable) && strpos($callable, "::")) {
193
+			$callable = explode("::", $callable);
194
+		}
195
+
196
+		if (is_array($callable)) {
197
+			if (is_string($callable[0])) {
198
+				   $callable[0] = $this->parseCallableController($callable[0], $container);
199
+			}
200
+
201
+			$callable[1] = $this->parseCallablePlaceholders($callable[1]);
202
+		}
203
+
204
+		return $callable;
205
+	}
206
+
207
+	/**
208
+	 * Get the controller object.
209
+	 *
210
+	 * @param string $controller
211
+	 * @param callable $container
212
+	 *
213
+	 * @return Object
214
+	 */
215
+
216
+	private function parseCallableController($controller, $container)
217
+	{
218
+		$controller  = rtrim($this->namespace, "\\") . "\\" . $this->parseCallablePlaceholders($controller);
219
+
220
+		if ($container === null) {
221
+			   return new $controller;
222
+		} else return $container($controller);
223
+	}
224
+
225
+	/**
226
+	 * Parse and replace dynamic content on route action.
227
+	 *
228
+	 * @param string $fragment Part of callable
229
+	 * @return string
230
+	 */
231
+
232
+	private function parseCallablePlaceholders($fragment)
233
+	{
234
+		if (strpos($fragment, "{") !== false) {
235
+			foreach ($this->params as $placeholder => $value) {
236
+				if (strpos($fragment, "{" . $placeholder . "}") !== false) {
237
+					$fragment = str_replace("{" . $placeholder . "}", ucwords(str_replace("-", " ", $value)), $fragment);
238
+				}
239
+			}
240
+		}
241
+
242
+		return $fragment;
243
+	}
244
+
245
+	/**
246
+	 * Execute the route action with the given strategy.
247
+	 *
248
+	 * @throws BadRouteException
249
+	 * @return mixed
250
+	 */
251
+
252
+	private function callWithStrategy()
253
+	{
254
+		if ($this->strategy instanceof StrategyInterface) {
255
+			if ($this->strategy instanceof MatcherAwareInterface) {
256
+				$this->strategy->setMatcher($this->matcher);
257
+			}
258
+
259
+			return $this->strategy->call($this);
260
+		}
261
+
262
+		throw new BadRouteException(str_replace("%s", get_class($this->strategy), BadRouteException::BAD_STRATEGY));
263
+	}
264
+
265
+	/**
266
+	 * @return Collector
267
+	 */
268
+
269
+	public function getCollector()
270
+	{
271
+		return $this->collector;
272
+	}
273
+
274
+	/**
275
+	 * @return string
276
+	 */
277
+
278
+	public function getMethod()
279
+	{
280
+		return $this->method;
281
+	}
282
+
283
+	/**
284
+	 * @return string
285
+	 */
286
+
287
+	public function getPattern()
288
+	{
289
+		return $this->pattern;
290
+	}
291
+
292
+	/**
293
+	 * @return string[]
294
+	 */
295
+
296
+	public function getSegments()
297
+	{
298
+		return explode("/", $this->pattern);
299
+	}
300
+
301
+	/**
302
+	 * @return callable
303
+	 */
304
+
305
+	public function getAction()
306
+	{
307
+		return $this->action;
308
+	}
309
+
310
+	/**
311
+	 * @return string
312
+	 */
313
+
314
+	public function getNamespace()
315
+	{
316
+		return $this->namespace;
317
+	}
318
+
319
+	/**
320
+	 * @return string[]
321
+	 */
322
+
323
+	public function getParams()
324
+	{
325
+		return $this->params;
326
+	}
327
+
328
+	/**
329
+	 * @param string $key
330
+	 * @return string
331
+	 */
332
+
333
+	public function getParam($key)
334
+	{
335
+		return $this->params[$key];
336
+	}
337
+
338
+	/**
339
+	 * Return defaults and params merged in one array.
340
+	 *
341
+	 * @return array
342
+	 */
343
+
344
+	public function getMergedParams()
345
+	{
346
+		return array_merge($this->defaults, $this->params);
347
+	}
348
+
349
+	/**
350
+	 * @return array
351
+	 */
352
+
353
+	public function getDefaults()
354
+	{
355
+		return $this->defaults;
356
+	}
357
+
358
+	/**
359
+	 * @param string $key
360
+	 * @return mixed
361
+	 */
362
+
363
+	public function getDefault($key)
364
+	{
365
+		return $this->defaults[$key];
366
+	}
367
+
368
+	/**
369
+	 * @return array
370
+	 */
371
+
372
+	public function getMetadataArray()
373
+	{
374
+		return $this->metadata;
375
+	}
376
+
377
+	/**
378
+	 * @param string $key
379
+	 * @return mixed
380
+	 */
381
+
382
+	public function getMetadata($key)
383
+	{
384
+		return $this->metadata[$key];
385
+	}
386
+
387
+	/**
388
+	 * @return string|null
389
+	 */
390
+
391
+	public function getStrategy()
392
+	{
393
+		if ($this->strategy instanceof StrategyInterface) {
394
+			return get_class($this->strategy);
395
+		}
396
+
397
+		return $this->strategy;
398
+	}
399
+
400
+	/**
401
+	 * @return StrategyInterface|string
402
+	 */
403
+
404
+	public function getRawStrategy()
405
+	{
406
+		return $this->strategy;
407
+	}
408
+
409
+	/**
410
+	 * @return Matcher
411
+	 */
412
+
413
+	public function getMatcher()
414
+	{
415
+		return $this->matcher;
416
+	}
417
+
418
+	/**
419
+	 * Verify if a Route have already been blocked.
420
+	 *
421
+	 * @return boolean
422
+	 */
423
+
424
+	public function getBlock()
425
+	{
426
+		return $this->blocked;
427
+	}
428
+
429
+	/**
430
+	 * Blocking a route indicate that that route have been selected and
431
+	 * parsed, now it will be given to the matcher.
432
+	 *
433
+	 * @param bool $blocked
434
+	 * @return self
435
+	 */
436
+
437
+	public function setBlock($blocked)
438
+	{
439
+		$this->blocked = $blocked;
440
+		return $this;
441
+	}
442
+
443
+	/**
444
+	 * @param string $method
445
+	 * @return Route
446
+	 */
447
+
448
+	public function setMethod($method)
449
+	{
450
+		$this->forget();
451
+		$this->method = $method;
452
+		return $this->reset();
453
+	}
454
+
455
+	/**
456
+	 * @param string $pattern
457
+	 * @return Route
458
+	 */
459
+
460
+	public function setPattern($pattern)
461
+	{
462
+		$this->forget();
463
+		$this->pattern = $pattern;
464
+		return $this->reset();
465
+	}
466
+
467
+	/**
468
+	 * @param string $pattern
469
+	 * @return self
470
+	 */
471
+
472
+	public function setPatternWithoutReset($pattern)
473
+	{
474
+		$this->pattern = $pattern;
475
+		return $this;
476
+	}
477
+
478
+	/**
479
+	 * @param string $action
480
+	 * @return self
481
+	 */
482
+
483
+	public function setAction($action)
484
+	{
485
+		$this->action = $action;
486
+		return $this;
487
+	}
488
+
489
+	/**
490
+	 * @param string $namespace
491
+	 * @return self
492
+	 */
493
+
494
+	public function setNamespace($namespace)
495
+	{
496
+		$this->namespace = $namespace;
497
+		return $this;
498
+	}
499
+
500
+	/**
501
+	 * @param string[] $params
502
+	 * @return self
503
+	 */
504
+
505
+	public function setParams(array $params)
506
+	{
507
+		$this->params = $params;
508
+		return $this;
509
+	}
510
+
511
+	/**
512
+	 * @param string $key
513
+	 * @param string $value
514
+	 *
515
+	 * @return self
516
+	 */
517
+
518
+	public function setParam($key, $value)
519
+	{
520
+		$this->params[$key] = $value;
521
+		return $this;
522
+	}
523
+
524
+	/**
525
+	 * @param mixed[] $defaults
526
+	 * @return self
527
+	 */
528
+
529
+	public function setDefaults(array $defaults)
530
+	{
531
+		$this->defaults = $defaults;
532
+		return $this;
533
+	}
534
+
535
+	/**
536
+	 * @param string $key
537
+	 * @param mixed $value
538
+	 *
539
+	 * @return self
540
+	 */
541
+
542
+	public function setDefault($key, $value)
543
+	{
544
+		$this->defaults[$key] = $value;
545
+		return $this;
546
+	}
547
+
548
+	/**
549
+	 * @param mixed[] $metadata
550
+	 * @return self
551
+	 */
552
+
553
+	public function setMetadataArray(array $metadata)
554
+	{
555
+		$this->metadata = $metadata;
556
+		return $this;
557
+	}
558
+
559
+	/**
560
+	 * @param string $key
561
+	 * @param mixed $value
562
+	 *
563
+	 * @return $this
564
+	 */
565
+
566
+	public function setMetadata($key, $value)
567
+	{
568
+		$this->metadata[$key] = $value;
569
+		return $this;
570
+	}
571
+
572
+	/**
573
+	 * @param null|string|StrategyInterface $strategy
574
+	 * @return self
575
+	 */
576
+
577
+	public function setStrategy($strategy)
578
+	{
579
+		$this->strategy = $strategy;
580
+		return $this;
581
+	}
582
+
583
+	/**
584
+	 * @param Matcher $matcher
585
+	 * @return self
586
+	 */
587
+
588
+	public function setMatcher(Matcher $matcher)
589
+	{
590
+		$this->matcher = $matcher;
591
+		return $this;
592
+	}
593
+
594
+	/**
595
+	 * Set a constraint to a token in the route pattern.
596
+	 *
597
+	 * @param string $token
598
+	 * @param string $regex
599
+	 *
600
+	 * @return self
601
+	 */
602
+
603
+	public function setConstraint($token, $regex)
604
+	{
605
+		$initPos = strpos($this->pattern, "{" . $token);
606
+
607
+		if ($initPos !== false) {
608
+			$endPos = strpos($this->pattern, "}", $initPos);
609
+			$newPattern = substr_replace($this->pattern, "{" . "$token:$regex" . "}", $initPos, $endPos - $initPos + 1);
610
+			$wildcards = $this->collector->getParser()->getWildcardTokens();
611
+			$newPattern = str_replace(array_keys($wildcards), $wildcards, $newPattern);
612
+			$this->setPatternWithoutReset($newPattern);
613
+		}
614
+
615
+		return $this;
616
+	}
617
+
618
+	/**
619
+	 * @param string $key
620
+	 * @return bool
621
+	 */
622
+
623
+	public function hasParam($key)
624
+	{
625
+		return isset($this->params[$key]);
626
+	}
627
+
628
+	/**
629
+	 * @param string $key
630
+	 * @return bool
631
+	 */
632
+
633
+	public function hasDefault($key)
634
+	{
635
+		return isset($this->defaults[$key]);
636
+	}
637
+
638
+	/**
639
+	 * @param string $key
640
+	 * @return bool
641
+	 */
642
+
643
+	public function hasMetadata($key)
644
+	{
645
+		return isset($this->metadata[$key]);
646
+	}
647 647
 
648 648
 }
Please login to merge, or discard this patch.