Passed
Push — master ( 2aae67...c4cd3f )
by Michael
07:21 queued 10s
created

Router::add()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 3
crap 2
1
<?php
2
3
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 * @version 1.0.0
9
 */
10
11
namespace miBadger\Router;
12
13
use miBadger\Http\ServerRequest;
14
use miBadger\Http\ServerResponse;
15
use miBadger\Http\ServerResponseException;
16
17
/**
18
 * The router class.
19
 *
20
 * @since 1.0.0
21
 */
22
class Router implements \IteratorAggregate
23
{
24
	/** @var string The base path. */
25
	private $basePath;
26
27
	/** @var array The routes. */
28
	private $routes;
29
30
	/**
31
	 * Construct a Router object with the given routers.
32
	 *
33
	 * @param string $basePath = ''
34
	 */
35 19
	public function __construct($basePath = '')
36
	{
37 19
		$this->basePath = $basePath;
38 19
		$this->routes = [];
39 19
	}
40
41
	/**
42
	 * Returns the base path.
43
	 *
44
	 * @return string the base path.
45
	 */
46 2
	public function getBasePath()
47
	{
48 2
		return $this->basePath;
49
	}
50
51
	/**
52
	 * Set the base path.
53
	 *
54
	 * @param string $basePath
55
	 * @return $this
56
	 */
57 2
	public function setBasePath($basePath)
58
	{
59 2
		$this->basePath = $basePath;
60
61 2
		return $this;
62
	}
63
64
	/**
65
	 * {@inheritdoc}
66
	 */
67 1
	public function getIterator()
68
	{
69 1
		return new \RecursiveArrayIterator($this->routes);
70
	}
71
72
	/**
73
	 * Returns the number of key-value mappings in the router map.
74
	 *
75
	 * @return int the number of key-value mappings in the router map.
76
	 */
77 1
	public function count()
78
	{
79 1
		$result = 0;
80
81 1
		foreach ($this->routes as $routes) {
82 1
			$result += count($routes);
83
		}
84
85 1
		return $result;
86
	}
87
88
	/**
89
	 * Returns true if the router map contains no key-value mappings.
90
	 *
91
	 * @return bool true if the router map contains no key-value mappings.
92
	 */
93 2
	public function isEmpty()
94
	{
95 2
		return empty($this->routes);
96
	}
97
98
	/**
99
	 * Returns true if the router map contains a mapping for the specified method & route.
100
	 *
101
	 * @param string $method
102
	 * @param string $route
103
	 * @return bool true if the static route map contains a mapping for the specified method & route.
104
	 */
105 2
	public function contains($method, $route)
106
	{
107 2
		return $this->get($method, $route) !== null;
108
	}
109
110
	/**
111
	 * Returns true if the router map maps one or more routes to the specified callable.
112
	 *
113
	 * @param callable $callable
114
	 * @return bool true if the router map maps one or more routes to the specified callable.
115
	 */
116 1
	public function containsCallable(callable $callable)
117
	{
118 1
		foreach ($this->routes as $method) {
119 1
			foreach ($method as $entry)
120
			{
121 1
				if ($entry->callable === $callable) {
122 1
					return true;
123
				}
124
			}
125
		}
126
127 1
		return false;
128
	}
129
130
	/**
131
	 * Returns the callable to which the specified route is mapped, or null if the router map contains no mapping for the route.
132
	 *
133
	 * @param string $method
134
	 * @param string $route
135
	 * @return callable the callable to which the specified route is mapped, or null if the router map contains no mapping for the route.
136
	 */
137 4
	public function get($method, $route)
138
	{
139 4
		if (!array_key_exists($method, $this->routes)) {
140 2
			return null;
141
		}
142
143 3
		foreach ($this->routes[$method] as $key => $entry) {
144 3
			if ($entry->route == $route) {
145 3
				return $entry->callable;
146
			}
147
		}
148
149 2
		return null;
150
	}
151
152
	/**
153
	 * Associates the specified callable with the specified method & route in the route map.
154
	 *
155
	 * @param string|array $methods
156
	 * @param string $route
157
	 * @param callable $callable
158
	 * @return $this
159
	 */
160 19
	public function set($methods, $route, callable $callable)
161
	{
162 19
		if (is_string($methods)) {
163 2
			$methods = [$methods];
164
		}
165
166 19
		foreach ($methods as $method) {
167 19
			$this->add($method, $route, $callable);
168
		}
169
170 19
		return $this;
171
	}
172
173
	/**
174
	 * Adds a method & route to the to the route map.
175
	 *
176
	 * @param string $method
177
	 * @param string $route
178
	 * @param callable $callable
179
	 */
180 19
	private function add(string $method, string $route, callable $callable)
181
	{
182
		$entry = (object)[
183 19
			'route' => $route,
184 19
			'pattern' => $this->createPattern($route),
185 19
			'callable' => $callable
186
		];
187
188 19
		if (!array_key_exists($method, $this->routes)) {
189 19
			$this->routes[$method] = [];
190
		}
191
192 19
		$this->routes[$method][] = $entry;
193 19
	}
194
195
	/**
196
	 * Creates a regex-enabled pattern from the route
197
	 *
198
	 * @param string $route
199
	 * @return string the pattern string.
200
	 */
201 19
	private function createPattern(string $route)
202
	{
203 19
		return '|^' . preg_replace('|\{[^\}]+\}|', '([^\/]+)', $route) . '$|';
204
	}
205
206
	/**
207
	 * Removes the mapping for the specified route from the router map if present.
208
	 *
209
	 * @param string $method
210
	 * @param string $route
211
	 * @return null
212
	 */
213 3
	public function remove($method, $route)
214
	{
215 3
		if (!array_key_exists($method, $this->routes)) {
216 1
			return;
217
		}
218
219 3
		foreach ($this->routes[$method] as $key => $entry) {
220 3
			if ($entry->route == $route) {
221 3
				unset($this->routes[$method][$key]);
222
			}
223
		}
224
225 3
		if (count($this->routes[$method]) == 0) {
226 3
			unset($this->routes[$method]);
227
		}
228 3
	}
229
230
	/**
231
	 * Removes all of the mappings from the router map.
232
	 *
233
	 * @return null
234
	 */
235 1
	public function clear()
236
	{
237 1
		$this->routes = [];
238 1
	}
239
240
	/**
241
	 * Returns the result of the given route's callable.
242
	 *
243
	 * @param string|null $method = new ServerRequest()->getMethod()
244
	 * @param string|null $route = new ServerRequest()->getUri()->getPath()
245
	 * @return mixed the result of the given route's callable.
246
	 * @throws ServerResponseException
247
	 */
248 8
	public function resolve($method = null, $route = null)
249
	{
250 8
		$serverRequest = new ServerRequest();
251
252 8
		if ($method === null) {
253 1
			$method = $serverRequest->getMethod();
254
		}
255
256 8
		if ($route === null) {
257 1
			$route = $serverRequest->getUri()->getPath();
258
		}
259
260 8
		if ($this->basePath !== '' && strpos($route, $this->basePath, 0) === 0) {
261 1
			$route = substr($route, strlen($this->basePath));
262
		}
263
264 8
		return $this->callCallable($this->getCallable($method, $route));
265
	}
266
267
	/**
268
	 * Returns the callable to which the specied method and route are mapped.
269
	 *
270
	 * @param string $method
271
	 * @param string $route
272
	 * @return array the callable to which the specied method and route are mapped and the route matches.
273
	 * @throws ServerResponseException
274
	 */
275 8
	private function getCallable($method, $route)
276
	{
277 8
		if (!array_key_exists($method, $this->routes)) {
278 1
			throw new ServerResponseException(new ServerResponse(404));
279
		}
280
281 7
		foreach ($this->routes[$method] as $entry) {
282 7
			if (preg_match($entry->pattern, $route, $matches) > 0) {
283 6
				array_shift($matches);
284 7
				return [$entry->callable, $matches];
285
			}
286
		}
287
288 1
		throw new ServerResponseException(new ServerResponse(404));
289
	}
290
291
	/**
292
	 * Returns the result of the callable.
293
	 *
294
	 * @param array the callable and the route matches.
295
	 * @return mixed the result the callable.
296
	 */
297 6
	private function callCallable($callable)
298
	{
299 6
		return call_user_func_array($callable[0], $callable[1]);
300
	}
301
}
302