Failed Conditions
Push — master ( 5de32f...4f0134 )
by Atanas
01:47
created

src/Routing/Router.php (1 issue)

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|null $old
90
	 * @param  string|array|\Closure|ConditionInterface|null $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;
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 the handler attribute taking the latest value.
138
	 *
139
	 * @param  callable|null $old
140
	 * @param  callable|null $new
141
	 * @return string|\Closure
142
	 */
143 1
	public function mergeQueryAttribute( $old, $new ) {
144 1
		if ( $new === null ) {
145 1
			return $old;
146
		}
147
148 1
		if ( $old === null ) {
149 1
			return $new;
150
		}
151
152 1
		return function ( $query_vars ) use ( $old, $new ) {
153 1
			return call_user_func( $new, call_user_func( $old,  $query_vars ) );
0 ignored issues
show
Expected 1 space instead of 2 after comma in function call.
Loading history...
154 1
		};
155
	}
156
157
	/**
158
	 * Merge attributes into route.
159
	 *
160
	 * @param  array<string, mixed> $old
161
	 * @param  array<string, mixed> $new
162
	 * @return array<string, mixed>
163
	 */
164 1
	public function mergeAttributes( $old, $new ) {
165
		$attributes = [
166 1
			'methods' => $this->mergeMethodsAttribute(
167 1
				(array) Arr::get( $old, 'methods', [] ),
168 1
				(array) Arr::get( $new, 'methods', [] )
169
			),
170
171 1
			'condition' => $this->mergeConditionAttribute(
172 1
				Arr::get( $old, 'condition', null ),
173 1
				Arr::get( $new, 'condition', null )
174
			),
175
176 1
			'middleware' => $this->mergeMiddlewareAttribute(
177 1
				(array) Arr::get( $old, 'middleware', [] ),
178 1
				(array) Arr::get( $new, 'middleware', [] )
179
			),
180
181 1
			'namespace' => $this->mergeNamespaceAttribute(
182 1
				Arr::get( $old, 'namespace', '' ),
183 1
				Arr::get( $new, 'namespace', '' )
184
			),
185
186 1
			'handler' => $this->mergeNamespaceAttribute(
187 1
				Arr::get( $old, 'handler', '' ),
188 1
				Arr::get( $new, 'handler', '' )
189
			),
190
191 1
			'query' => $this->mergeQueryAttribute(
192 1
				Arr::get( $old, 'query', null ),
193 1
				Arr::get( $new, 'query', null )
194
			),
195
		];
196
197 1
		return $attributes;
198
	}
199
200
	/**
201
	 * Get the top group from the stack.
202
	 *
203
	 * @codeCoverageIgnore
204
	 * @return array<string, mixed>
205
	 */
206
	protected function getGroup() {
207
		return Arr::last( $this->group_stack, null, [] );
208
	}
209
210
	/**
211
	 * Add a group to the group stack, merging all previous attributes.
212
	 *
213
	 * @codeCoverageIgnore
214
	 * @param array<string, mixed> $group
215
	 * @return void
216
	 */
217
	protected function pushGroup( $group ) {
218
		$this->group_stack[] = $this->mergeAttributes( $this->getGroup(), $group );
219
	}
220
221
	/**
222
	 * Remove last group from the group stack.
223
	 *
224
	 * @codeCoverageIgnore
225
	 * @return void
226
	 */
227
	protected function popGroup() {
228
		array_pop( $this->group_stack );
229
	}
230
231
	/**
232
	 * Create a route group.
233
	 *
234
	 * @codeCoverageIgnore
235
	 * @param array<string, mixed> $attributes
236
	 * @param \Closure|string      $routes Closure or path to file.
237
	 * @return void
238
	 */
239
	public function group( $attributes, $routes ) {
240
		$this->pushGroup( $attributes );
241
242
		if ( is_string( $routes ) ) {
243
			/** @noinspection PhpIncludeInspection */
244
			/** @codeCoverageIgnore */
245
			require_once $routes;
246
		} else {
247
			$routes();
248
		}
249
250
		$this->popGroup();
251
	}
252
253
	/**
254
	 * Make a route condition.
255
	 *
256
	 * @param  mixed              $condition
257
	 * @return ConditionInterface
258
	 */
259 3
	protected function routeCondition( $condition ) {
260 3
		if ( $condition === null ) {
261 1
			throw new ConfigurationException( 'No route condition specified. Did you miss to call url() or where()?' );
262
		}
263
264 2
		if ( ! $condition instanceof ConditionInterface ) {
265 1
			$condition = $this->condition_factory->make( $condition );
266
		}
267
268 2
		return $condition;
269
	}
270
271
	/**
272
	 * Make a route handler.
273
	 *
274
	 * @codeCoverageIgnore
275
	 * @param  string|\Closure|null $handler
276
	 * @param  string               $namespace
277
	 * @return Handler
278
	 */
279
	protected function routeHandler( $handler, $namespace ) {
280
		return new Handler( $handler, '', $namespace );
281
	}
282
283
	/**
284
	 * Make a route.
285
	 *
286
	 * @param  array<string, mixed> $attributes
287
	 * @return RouteInterface
288
	 */
289 1
	public function route( $attributes ) {
290 1
		$attributes = $this->mergeAttributes( $this->getGroup(), $attributes );
291
292 1
		$methods = Arr::get( $attributes, 'methods', [] );
293 1
		$condition = Arr::get( $attributes, 'condition', null );
294 1
		$handler = Arr::get( $attributes, 'handler', '' );
295 1
		$namespace = Arr::get( $attributes, 'namespace', '' );
296
297 1
		$condition = $this->routeCondition( $condition );
298 1
		$handler = $this->routeHandler( $handler, $namespace );
299
300 1
		$route = new Route( $methods, $condition, $handler );
301
302 1
		$route->decorate( $attributes );
303
304 1
		return $route;
305
	}
306
307
	/**
308
	 * Assign and return the first satisfied route (if any) as the current one for the given request.
309
	 *
310
	 * @param  RequestInterface $request
311
	 * @return RouteInterface
312
	 */
313 2
	public function execute( $request ) {
314
		/** @var $routes \WPEmerge\Routing\RouteInterface[] */
315 2
		$routes = $this->getRoutes();
316
317 2
		foreach ( $routes as $route ) {
318 2
			if ( $route->isSatisfied( $request ) ) {
319 1
				$this->setCurrentRoute( $route );
320 2
				return $route;
321
			}
322
		}
323
324 1
		return null;
325
	}
326
}
327