Passed
Branch refactor/kernels (6aa266)
by Atanas
01:51
created

Router   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Test Coverage

Coverage 98.33%

Importance

Changes 0
Metric Value
eloc 66
dl 0
loc 276
ccs 59
cts 60
cp 0.9833
rs 10
c 0
b 0
f 0
wmc 26

17 Methods

Rating   Name   Duplication   Size   Complexity  
A group() 0 11 2
A getGroup() 0 2 1
A mergeAttributes() 0 29 1
A routeHandler() 0 2 1
A __construct() 0 2 1
A pushGroup() 0 2 1
A mergeMethodsAttribute() 0 2 1
A mergeConditionAttribute() 0 8 3
A popGroup() 0 2 1
A mergeNamespaceAttribute() 0 2 2
A setCurrentRoute() 0 2 1
A route() 0 17 1
A routeCondition() 0 10 3
A mergeMiddlewareAttribute() 0 2 1
A execute() 0 11 3
A getCurrentRoute() 0 2 1
A mergeHandlerAttribute() 0 2 2
1
<?php
2
/**
3
 * @package   WPEmerge
4
 * @author    Atanas Angelov <[email protected]>
5
 * @copyright 2018 Atanas Angelov
6
 * @license   https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0
7
 * @link      https://wpemerge.com/
8
 */
9
10
namespace WPEmerge\Routing;
11
12
use WPEmerge\Exceptions\ConfigurationException;
13
use WPEmerge\Helpers\Handler;
14
use WPEmerge\Requests\RequestInterface;
15
use WPEmerge\Routing\Conditions\ConditionFactory;
16
use WPEmerge\Routing\Conditions\ConditionInterface;
17
use WPEmerge\Support\Arr;
18
19
/**
20
 * Provide routing for site requests (i.e. all non-api requests).
21
 */
22
class Router implements HasRoutesInterface {
23
	use HasRoutesTrait;
24
25
	/**
26
	 * Condition factory.
27
	 *
28
	 * @var ConditionFactory
29
	 */
30
	protected $condition_factory = null;
31
32
	/**
33
	 * Group stack.
34
	 *
35
	 * @var array<array<string, mixed>>
36
	 */
37
	protected $group_stack = [];
38
39
	/**
40
	 * Current active route.
41
	 *
42
	 * @var RouteInterface
43
	 */
44
	protected $current_route = null;
45
46
	/**
47
	 * Constructor.
48
	 *
49
	 * @codeCoverageIgnore
50
	 * @param ConditionFactory $condition_factory
51
	 */
52
	public function __construct( ConditionFactory $condition_factory ) {
53
		$this->condition_factory = $condition_factory;
54
	}
55
56
	/**
57
	 * Get the current route.
58
	 *
59
	 * @return RouteInterface
60
	 */
61 1
	public function getCurrentRoute() {
62 1
		return $this->current_route;
63
	}
64
65
	/**
66
	 * Set the current route.
67
	 *
68
	 * @param  RouteInterface
69
	 * @return void
70
	 */
71 1
	public function setCurrentRoute( RouteInterface $current_route ) {
72 1
		$this->current_route = $current_route;
73 1
	}
74
75
	/**
76
	 * Merge the methods attribute combining values.
77
	 *
78
	 * @param  array<string> $old
79
	 * @param  array<string> $new
80
	 * @return array<string>
81
	 */
82 1
	public function mergeMethodsAttribute( $old, $new ) {
83 1
		return array_merge( $old, $new );
84
	}
85
86
	/**
87
	 * Merge the condition attribute.
88
	 *
89
	 * @param  string|array|\Closure|ConditionInterface $old
90
	 * @param  string|array|\Closure|ConditionInterface $new
91
	 * @return ConditionInterface|string
92
	 */
93 3
	public function mergeConditionAttribute( $old, $new ) {
94
		try {
95 3
			$condition = $this->condition_factory->merge( $old, $new );
96 1
		} catch ( ConfigurationException $e ) {
97 1
			throw new ConfigurationException( 'Route condition is not a valid route string or condition.' );
98
		}
99
100 2
		return $condition !== null ? $condition : '';
101
	}
102
103
	/**
104
	 * Merge the middleware attribute combining values.
105
	 *
106
	 * @param  array<string> $old
107
	 * @param  array<string> $new
108
	 * @return array<string>
109
	 */
110 1
	public function mergeMiddlewareAttribute( $old, $new ) {
111 1
		return array_merge( $old, $new );
112
	}
113
114
	/**
115
	 * Merge the namespace attribute taking the latest value.
116
	 *
117
	 * @param  string $old
118
	 * @param  string $new
119
	 * @return string
120
	 */
121 1
	public function mergeNamespaceAttribute( $old, $new ) {
122 1
		return ! empty( $new ) ? $new : $old;
123
	}
124
125
	/**
126
	 * Merge the handler attribute taking the latest value.
127
	 *
128
	 * @param  string|\Closure $old
129
	 * @param  string|\Closure $new
130
	 * @return string|\Closure
131
	 */
132 1
	public function mergeHandlerAttribute( $old, $new ) {
133 1
		return ! empty( $new ) ? $new : $old;
134
	}
135
136
	/**
137
	 * Merge attributes into route.
138
	 *
139
	 * @param  array<string, mixed> $old
140
	 * @param  array<string, mixed> $new
141
	 * @return array<string, mixed>
142
	 */
143 1
	public function mergeAttributes( $old, $new ) {
144
		$attributes = [
145 1
			'methods' => $this->mergeMethodsAttribute(
146 1
				(array) Arr::get( $old, 'methods', [] ),
147 1
				(array) Arr::get( $new, 'methods', [] )
148
			),
149
150 1
			'condition' => $this->mergeConditionAttribute(
151 1
				Arr::get( $old, 'condition', '' ),
152 1
				Arr::get( $new, 'condition', '' )
153
			),
154
155 1
			'middleware' => $this->mergeMiddlewareAttribute(
156 1
				(array) Arr::get( $old, 'middleware', [] ),
157 1
				(array) Arr::get( $new, 'middleware', [] )
158
			),
159
160 1
			'namespace' => $this->mergeNamespaceAttribute(
161 1
				Arr::get( $old, 'namespace', '' ),
162 1
				Arr::get( $new, 'namespace', '' )
163
			),
164
165 1
			'handler' => $this->mergeNamespaceAttribute(
166 1
				Arr::get( $old, 'handler', '' ),
167 1
				Arr::get( $new, 'handler', '' )
168
			),
169
		];
170
171 1
		return $attributes;
172
	}
173
174
	/**
175
	 * Get the top group from the stack.
176
	 *
177
	 * @codeCoverageIgnore
178
	 * @return array<string, mixed>
179
	 */
180
	protected function getGroup() {
181
		return Arr::last( $this->group_stack, null, [] );
182
	}
183
184
	/**
185
	 * Add a group to the group stack, merging all previous attributes.
186
	 *
187
	 * @codeCoverageIgnore
188
	 * @param array<string, mixed> $group
189
	 * @return void
190
	 */
191
	protected function pushGroup( $group ) {
192
		$this->group_stack[] = $this->mergeAttributes( $this->getGroup(), $group );
193
	}
194
195
	/**
196
	 * Remove last group from the group stack.
197
	 *
198
	 * @codeCoverageIgnore
199
	 * @return void
200
	 */
201
	protected function popGroup() {
202
		array_pop( $this->group_stack );
203
	}
204
205
	/**
206
	 * Create a route group.
207
	 *
208
	 * @codeCoverageIgnore
209
	 * @param array<string, mixed> $attributes
210
	 * @param \Closure|string      $routes Closure or path to file.
211
	 * @return void
212
	 */
213
	public function group( $attributes, $routes ) {
214
		$this->pushGroup( $attributes );
215
216
		if ( is_string( $routes ) ) {
217
			// @codeCoverageIgnore
218
			require_once $routes;
219
		} else {
220
			$routes();
221
		}
222
223
		$this->popGroup();
224
	}
225
226
	/**
227
	 * Make a route condition.
228
	 *
229
	 * @param  mixed              $condition
230
	 * @return ConditionInterface
231
	 */
232 3
	protected function routeCondition( $condition ) {
233 3
		if ( $condition === '' ) {
234 1
			throw new ConfigurationException( 'No route condition specified. Did you miss to call url() or where()?' );
235
		}
236
237 2
		if ( ! $condition instanceof ConditionInterface ) {
238
			$condition = $this->condition_factory->make( $condition );
239
		}
240
241 2
		return $condition;
242
	}
243
244
	/**
245
	 * Make a route handler.
246
	 *
247
	 * @codeCoverageIgnore
248
	 * @param  string|\Closure|null $handler
249
	 * @param  string               $namespace
250
	 * @return Handler
251
	 */
252
	protected function routeHandler( $handler, $namespace ) {
253
		return new Handler( $handler, '', $namespace );
254
	}
255
256
	/**
257
	 * Make a route.
258
	 *
259
	 * @param  array<string, mixed> $attributes
260
	 * @return RouteInterface
261
	 */
262 1
	public function route( $attributes ) {
263 1
		$attributes = $this->mergeAttributes( $this->getGroup(), $attributes );
264
265 1
		$methods = Arr::get( $attributes, 'methods', [] );
266 1
		$condition = Arr::get( $attributes, 'condition', null );
267 1
		$handler = Arr::get( $attributes, 'handler', '' );
268 1
		$namespace = Arr::get( $attributes, 'namespace', '' );
269 1
		$middleware = Arr::get( $attributes, 'middleware', [] );
270
271 1
		$condition = $this->routeCondition( $condition );
272 1
		$handler = $this->routeHandler( $handler, $namespace );
273
274 1
		$route = new Route( $methods, $condition, $handler );
275
276 1
		$route->middleware( $middleware );
277
278 1
		return $route;
279
	}
280
281
	/**
282
	 * Assign and return the first satisfied route (if any) as the current one for the given request.
283
	 *
284
	 * @param  RequestInterface $request
285
	 * @return RouteInterface
286
	 */
287 2
	public function execute( $request ) {
288 2
		$routes = $this->getRoutes();
289
290 2
		foreach ( $routes as $route ) {
291 2
			if ( $route->isSatisfied( $request ) ) {
292 1
				$this->setCurrentRoute( $route );
293 2
				return $route;
294
			}
295
		}
296
297 1
		return null;
298
	}
299
}
300