Passed
Push — develop ( 30650d...a746c9 )
by Berend
09:32
created

AccessRouter::getCallable()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 10
cts 10
cp 1
rs 8.9777
c 0
b 0
f 0
cc 6
nc 3
nop 2
crap 6
1
<?php
2
3
namespace miBadger\Router;
4
5
use miBadger\Http\ServerRequest;
6
use miBadger\Http\ServerResponse;
7
use miBadger\Http\ServerResponseException;
8
9
class AccessRouter
10
{
11
	private $routes; // $routes[method] = (route, pattern, callable, permission)
12
13
	private $basePath;
14
15
	private $user;
16
17 9
	public function __construct(PermissionCheckable $user = null, $basePath = '')
18
	{
19 9
		$this->user = $user;
20 9
		$this->routes = [];
21 9
		$this->basePath = $basePath;
22 9
	}
23
	/**
24
	 * Returns the base path.
25
	 *
26
	 * @return string the base path.
27
	 */
28
	public function getBasePath()
29
	{
30
		return $this->basePath;
31
	}
32
33
	/**
34
	 * Set the base path.
35
	 *
36
	 * @param string $basePath
37
	 * @return $this
38
	 */
39
	public function setBasePath($basePath)
40
	{
41
		$this->basePath = $basePath;
42
43
		return $this;
44
	}
45
46
	/**
47
	 * {@inheritdoc}
48
	 */
49
	public function getIterator()
50
	{
51
		return new \RecursiveArrayIterator($this->routes);
52
	}
53
54
	/**
55
	 * Returns the number of key-value mappings in the router map.
56
	 *
57
	 * @return int the number of key-value mappings in the router map.
58
	 */
59
	public function count()
60
	{
61
		$result = 0;
62
63
		foreach ($this->routes as $routes) {
64
			$result += count($routes);
65
		}
66
67
		return $result;
68
	}
69
70 9 View Code Duplication
	public function add(string $method, string $route, $permission, callable $callable)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
71
	{
72
		$entry = (object)[
73 9
			'route' => $route,
74 9
			'pattern' => $this->createPattern($route),
75 9
			'permission' => $permission,
76 9
			'callable' => $callable
77
		];
78
79 9
		if (!array_key_exists($method, $this->routes)) {
80 9
			$this->routes[$method] = [];
81
		}
82
83 9
		$this->routes[$method][] = $entry;
84 9
	}
85
86
	/**
87
	 * Creates a regex-enabled pattern from the route
88
	 *
89
	 * @param string $route
90
	 * @return string the pattern string.
91
	 */
92 9
	private function createPattern(string $route)
93
	{
94 9
		return '|^' . preg_replace('|\{[^\}]+\}|', '([^\/]+)', $route) . '$|';
95
	}
96
97
	/**
98
	 * Removes the mapping for the specified route from the router map if present.
99
	 *
100
	 * @param string $method
101
	 * @param string $route
102
	 * @return null
103
	 */
104
	public function remove(string $method, string $route, $permission)
105
	{
106
		if (!array_key_exists($method, $this->routes)) {
107
			return;
108
		}
109
110
		foreach ($this->routes[$method] as $key => $entry) {
111
			if ($entry->route == $route && $entry->permission == $permission) {
112
				unset($this->routes[$method][$key]);
113
			}
114
		}
115
116
		if (count($this->routes[$method]) == 0) {
117
			unset($this->routes[$method]);
118
		}
119
	}
120
121
	/**
122
	 * Removes all of the mappings from the router map.
123
	 *
124
	 * @return null
125
	 */
126
	public function clear()
127
	{
128
		$this->routes = [];
129
	}
130
131
132
	/**
133
	 * Returns the result of the given route's callable.
134
	 *
135
	 * @param string|null $method = new ServerRequest()->getMethod()
136
	 * @param string|null $route = new ServerRequest()->getUri()->getPath()
137
	 * @return mixed the result of the given route's callable.
138
	 * @throws ServerResponseException
139
	 */
140 8 View Code Duplication
	public function resolve($method = null, $route = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
	{
142 8
		$serverRequest = new ServerRequest();
143
144 8
		if ($method === null) {
145 1
			$method = $serverRequest->getMethod();
146
		}
147
148 8
		if ($route === null) {
149 1
			$route = $serverRequest->getUri()->getPath();
150
		}
151
152 8
		if ($this->basePath !== '' && strpos($route, $this->basePath, 0) === 0) {
153
			$route = substr($route, strlen($this->basePath));
154
		}
155
156 8
		return $this->callCallable($this->getCallable($method, $route));
157
	}
158
159
	/**
160
	 * Returns the result of the callable.
161
	 *
162
	 * @param array the callable and the route matches.
163
	 * @return mixed the result the callable.
164
	 */
165 4
	private function callCallable($callable)
166
	{
167 4
		return call_user_func_array($callable[0], $callable[1]);
168
	}
169
170 8
	public function getMatchingRoutes($method, $route)
171
	{
172 8
		if (!array_key_exists($method, $this->routes)) {
173 1
			throw new ServerResponseException(new ServerResponse(404));
174
		}
175
176 7
		$matchedRoutes = [];
177
178 7 View Code Duplication
		foreach ($this->routes[$method] as $entry) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179 7
			if (preg_match($entry->pattern, $route, $matches) > 0) {
180
				// At this point the route is matched
181 6
				array_shift($matches);
182 7
				$matchedRoutes[] = [$route, $entry, $matches];
183
			}
184
		}
185
186 7
		if (count($matchedRoutes) == 0) {
187 1
			throw new ServerResponseException(new ServerResponse(404));		
188
		}
189
190 6
		return $matchedRoutes;
191
	}
192
193 8
	protected function getCallable($method, $route)
194
	{
195
		// Check if permissions are alright
196 8
		$matchedRoutes = $this->getMatchingRoutes($method, $route);
197
198 6
		foreach ($matchedRoutes as $match) {
199 6
			$route = $match[0];
0 ignored issues
show
Unused Code introduced by
$route is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
200 6
			$entry = $match[1];
201 6
			$matches = $match[2];
202
203 6
			if ($entry->permission === null 
204 6
				|| ($entry->permission !== null && $this->user !== null && $this->user->hasPermission($entry->permission)))
205
			{
206 6
				return [$entry->callable, $matches];
207
			}
208
209
		}
210
211 2
		throw new ServerResponseException(new ServerResponse(401));
212
	}
213
214
	/**
215
	 * Returns the cleaned ({token} => %s) permission map for the router 
216
	 * (associative map from route to the set of permissions that have access to that route), 
217
	 * 		null if a route has wildcard permissions
218
	 */
219 1
	public function makeGETRoutePermissionsMap()
220
	{
221 1
		$outputMap = [];
222
223 1
		foreach ($this->routes["GET"] as $entry) {
224
225
			// Extract clean route by replacing every slug occurrence with %s
226 1
			$slugFreeRoute = preg_replace('/({.*?})/', '%s', $entry->route);
227
228 1
			if ($entry->permission === null) {
229 1
				$outputMap[$slugFreeRoute] = null;	
230 1
				continue;
231
			}
232
233 1
			if (!isset($outputMap[$slugFreeRoute])) {
234 1
				$outputMap[$slugFreeRoute] = [];
235
			}
236
237 1
			$outputMap[$slugFreeRoute][] = $entry->permission;
238
		}
239
240 1
		return $outputMap;
241
	}
242
}