Passed
Branch refactor/route-groups (cbbc8f)
by Atanas
01:54
created

Router::setCurrentRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
nc 1
nop 1
dl 0
loc 2
c 0
b 0
f 0
cc 1
ccs 2
cts 2
cp 1
crap 1
rs 10
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 Exception;
13
use WPEmerge\Exceptions\ErrorHandlerInterface;
14
use WPEmerge\Facades\Framework;
15
use WPEmerge\Requests\RequestInterface;
16
use WPEmerge\Routing\Conditions\ConditionFactory;
17
use WPEmerge\Routing\Conditions\HasUrlWhereInterface;
18
use WPEmerge\Support\Arr;
19
20
/**
21
 * Provide routing for site requests (i.e. all non-api requests)
22
 */
23
class Router implements HasRoutesInterface {
24
	use HasRoutesTrait {
25
		addRoute as traitAddRoute;
26
	}
27
28
	/**
29
	 * Current request.
30
	 *
31
	 * @var RequestInterface
32
	 */
33
	protected $request = null;
34
35
	/**
36
	 * Condition factory.
37
	 *
38
	 * @var ConditionFactory
39
	 */
40
	protected $condition_factory = null;
41
42
	/**
43
	 * Global middleware.
44
	 *
45
	 * @var array
46
	 */
47
	protected $middleware = [];
48
49
	/**
50
	 * Global middleware priority.
51
	 *
52
	 * @var array
53
	 */
54
	protected $middleware_priority = [];
55
56
	/**
57
	 * Default global middleware priority.
58
	 *
59
	 * @var integer
60
	 */
61
	protected $default_middleware_priority = 0;
62
63
	/**
64
	 * Exception handler.
65
	 *
66
	 * @var ErrorHandlerInterface
67
	 */
68
	protected $error_handler = null;
69
70
	/**
71
	 * Current active route.
72
	 *
73
	 * @var RouteInterface
74
	 */
75
	protected $current_route = null;
76
77
	/**
78
	 * Group stack.
79
	 *
80
	 * @var array<array<string, mixed>>
81
	 */
82
	protected $group_stack = [];
83
84
	/**
85
	 * Constructor.
86
	 *
87
	 * @codeCoverageIgnore
88
	 *
89
	 * @param RequestInterface      $request
90
	 * @param ConditionFactory      $condition_factory
91
	 * @param array                 $middleware
92
	 * @param array                 $middleware_priority
93
	 * @param integer               $default_middleware_priority
94
	 * @param ErrorHandlerInterface $error_handler
95
	 */
96
	public function __construct(
97
		RequestInterface $request,
98
		ConditionFactory $condition_factory,
99
		$middleware,
100
		$middleware_priority,
101
		$default_middleware_priority,
102
		ErrorHandlerInterface $error_handler
103
	) {
104
		$this->request = $request;
105
		$this->condition_factory = $condition_factory;
106
		$this->middleware_priority = $middleware_priority;
107
		$this->default_middleware_priority = $default_middleware_priority;
108
		$this->middleware = $this->sortMiddleware( $middleware );
109
		$this->error_handler = $error_handler;
110
	}
111
112
	/**
113
	 * Hook into WordPress actions.
114
	 *
115
	 * @codeCoverageIgnore
116
	 * @return void
117
	 */
118
	public function boot() {
119
		add_action( 'template_include', [$this, 'execute'], 1000 );
120
	}
121
122
	/**
123
	 * Get middleware priority.
124
	 *
125
	 * @param  mixed   $middleware
126
	 * @return integer
127
	 */
128 1
	public function getMiddlewarePriority( $middleware ) {
129 1
		if ( is_string( $middleware ) && isset( $this->middleware_priority[ $middleware ] ) ) {
130 1
			return $this->middleware_priority[ $middleware ];
131
		}
132
133 1
		return $this->default_middleware_priority;
134
	}
135
136
	/**
137
	 * Sort middleware by priority in ascending order.
138
	 *
139
	 * @param  array $middleware
140
	 * @return array
141
	 */
142
	public function sortMiddleware( $middleware ) {
143 1
		usort( $middleware, function ( $a, $b ) use ( $middleware ) {
144 1
			$priority = $this->getMiddlewarePriority( $a ) - $this->getMiddlewarePriority( $b );
145
146 1
			if ( $priority !== 0 ) {
147 1
				return $priority;
148
			}
149
150
			// Keep original array order.
151 1
			return array_search( $a, $middleware ) - array_search( $b, $middleware );
152 1
		} );
153
154 1
		return array_values( $middleware );
155
	}
156
157
	/**
158
	 * Get the current route.
159
	 *
160
	 * @return RouteInterface
161
	 */
162 1
	public function getCurrentRoute() {
163 1
		return $this->current_route;
164
	}
165
166
	/**
167
	 * Set the current route.
168
	 *
169
	 * @param  RouteInterface
170
	 * @return void
171
	 */
172 1
	public function setCurrentRoute( RouteInterface $current_route ) {
173 1
		$this->current_route = $current_route;
174 1
	}
175
176
	/**
177
	 * Add a group to the group stack, mergin all previous attributes.
178
	 *
179
	 * @param array<string, mixed> $attributes
180
	 * @return void
181
	 */
182
	protected function addGroupToStack( $attributes ) {
183
		$previous = Arr::last( $this->group_stack, null, [] );
184
185
		$condition = $this->condition_factory->merge(
186
			Arr::get( $previous, 'condition', '' ),
187
			Arr::get( $attributes, 'condition', '' )
188
		);
189
190
		$attributes = array(
191
			'condition' => $condition !== null ? $condition : '',
192
			'where' => array_merge(
193
				Arr::get( $previous, 'where', [] ),
194
				Arr::get( $attributes, 'where', [] )
195
			),
196
			'middleware' => array_merge(
197
				(array) Arr::get( $previous, 'middleware', [] ),
198
				(array) Arr::get( $attributes, 'middleware', [] )
199
			),
200
		);
201
202
		$this->group_stack[] = $attributes;
203
	}
204
205
	/**
206
	 * Remove last group from the group stack.
207
	 *
208
	 * @return void
209
	 */
210
	protected function removeLastGroupFromStack() {
211
		array_pop( $this->group_stack );
212
	}
213
214
	/**
215
	 * Create a new route group.
216
	 *
217
	 * @param array<string, mixed> $attributes
218
	 * @param \Closure            $routes
219
	 * @return void
220
	 */
221 1
	public function group( $attributes, $routes ) {
222 1
		$this->addGroupToStack( $attributes );
223
224 1
		$routes();
225
226 1
		$this->removeLastGroupFromStack();
227 1
	}
228
229
	/**
230
	 * {@inheritDoc}
231
	 */
232 2
	public function addRoute( $route ) {
233 2
		$group = Arr::last( $this->group_stack, null, [] );
234 2
		$condition = $route->getCondition();
235
236 2
		if ( $condition instanceof HasUrlWhereInterface ) {
237
			$condition->setUrlWhere( array_merge(
238
				Arr::get( $group, 'where', [] ),
239
				$condition->getUrlWhere()
240
			) );
241
		}
242
243 2
		$condition = $this->condition_factory->merge(
244 2
			Arr::get( $group, 'condition', '' ),
245 2
			$condition
246
		);
247
248 2
		$route->setCondition( $condition );
249
250 2
		$route->setMiddleware( array_merge(
251 2
			$this->middleware,
252 2
			Arr::get( $group, 'middleware', [] ),
253 2
			$route->getMiddleware()
254
		) );
255
256 2
		return $this->traitAddRoute( $route );
257
	}
258
259
	/**
260
	 * Execute the first satisfied route, if any.
261
	 *
262
	 * @internal
263
	 * @param  string $view
264
	 * @return string
265
	 * @throws Exception
266
	 */
267 5
	public function execute( $view ) {
268 5
		$routes = $this->getRoutes();
269
270 5
		foreach ( $routes as $route ) {
271 5
			if ( $route->isSatisfied( $this->request ) ) {
272 4
				$this->setCurrentRoute( $route );
273 5
				return $this->handle( $this->request, $route, $view );
274
			}
275
		}
276
277 1
		return $view;
278
	}
279
280
	/**
281
	 * Execute a route.
282
	 *
283
	 * @throws Exception
284
	 * @param  RequestInterface $request
285
	 * @param  RouteInterface   $route
286
	 * @param  string           $view
287
	 * @return string
288
	 */
289 3
	protected function handle( RequestInterface $request, RouteInterface $route, $view ) {
290
		try {
291 3
			$this->error_handler->register();
292 3
			$response = $route->handle( $request, $view );
293 2
			$this->error_handler->unregister();
294 1
		} catch ( Exception $e ) {
295 1
			$response = $this->error_handler->getResponse( $e );
296
		}
297
298 2
		$container = Framework::getContainer();
299 2
		$container[ WPEMERGE_RESPONSE_KEY ] = $response;
300
301 2
		return WPEMERGE_DIR . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'view.php';
302
	}
303
304
	/**
305
	 * Handle ALL requests.
306
	 *
307
	 * @param  string|\Closure|null $handler
308
	 * @return RouteInterface
309
	 */
310 1
	public function handleAll( $handler = null ) {
311
		// match ANY request method
312
		// match ANY url
313
		// by default, use built-in WordPress controller
314 1
		return $this->any( '*', $handler );
315
	}
316
}
317