PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Http / Router.php

Router.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.10.0, at vendor/wpfluent/framework/src/WPFluent/Http/Router.php

597 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Http;
4
5 use Closure;
6
7 class Router
8 {
9 /**
10 * Application Instance
11 * @var \FluentCommunity\Framework\Foundation\Application
12 */
13 protected $app = null;
14
15 /**
16 * Mapping of named routes.
17 * @var array
18 */
19 protected $namedRoutes = [];
20
21 /**
22 * Registered routes collection
23 * @var array
24 */
25 protected $routes = [];
26
27 /**
28 * Attributes staged by chained calls (prefix(), name(),
29 * withPolicy(), before(), ...) that have not been claimed
30 * yet. The next group() or route declaration claims them.
31 * @var array
32 */
33 protected $staged = [];
34
35 /**
36 * Effective attributes of the group whose callback is
37 * currently executing. Empty outside any group.
38 * @var array
39 */
40 protected $context = [];
41
42 /**
43 * Whether routes created by this router should override existing ones.
44 * @var bool
45 */
46 protected $shouldOverride = false;
47
48 /**
49 * Weak references to groups pending execution.
50 * @var array
51 */
52 protected $pendingGroups = [];
53
54 /**
55 * Construct the routet instance
56 * @param \FluentCommunity\Framework\Foundation\Application $app
57 */
58 public function __construct($app)
59 {
60 $this->app = $app;
61 $this->staged = $this->newAttributes();
62 $this->context = $this->newAttributes();
63 }
64
65 /**
66 * Create a route group.
67 *
68 * The group captures its effective attributes (the enclosing
69 * group's attributes merged with anything staged for it) at
70 * creation time, so its callback resolves the same routes no
71 * matter when it runs: at end of statement, or later from
72 * registerRoutes() when the instance was kept alive.
73 *
74 * @param array|Closure $attributes
75 * @param Closure|null $callback
76 * @return \FluentCommunity\Framework\Http\Group
77 */
78 public function group($attributes = [], ?Closure $callback = null)
79 {
80 if ($attributes instanceof Closure) {
81 $callback = $attributes;
82 $attributes = [];
83 }
84
85 if (isset($attributes['name'])) {
86 $this->name($attributes['name']);
87 }
88
89 if (isset($attributes['prefix'])) {
90 $this->prefix($attributes['prefix']);
91 }
92
93 if (isset($attributes['namespace'])) {
94 $this->namespace($attributes['namespace']);
95 }
96
97 if (isset($attributes['policy'])) {
98 $this->withPolicy($attributes['policy']);
99 }
100
101 if (isset($attributes['middleware'])) {
102 $middleware = $attributes['middleware'];
103
104 if (isset($middleware['before'])) {
105 $this->middleware('before', $middleware['before']);
106 }
107
108 if (isset($middleware['after'])) {
109 $this->middleware('after', $middleware['after']);
110 }
111 }
112
113 return new Group($this, $callback, $this->resolveAttributes());
114 }
115
116 /**
117 * Set the route name
118 *
119 * @param string $name
120 * @return self
121 */
122 public function name($name)
123 {
124 $this->staged['name'][] = $name;
125
126 return $this;
127 }
128
129 /**
130 * Set the route prefix
131 *
132 * @param string $prefix
133 * @return self
134 */
135 public function prefix($prefix)
136 {
137 $this->staged['prefix'][] = $prefix;
138
139 return $this;
140 }
141
142 /**
143 * Set the namespace for the action/controller
144 *
145 * @param string $ns
146 * @return self
147 */
148 public function namespace($ns)
149 {
150 $this->staged['namespace'][] = $ns;
151
152 return $this;
153 }
154
155 /**
156 * Set the default route policy.
157 *
158 * @return self
159 */
160 public function withDefaultPolicy()
161 {
162 return $this->withPolicy(
163 // @phpstan-ignore-next-line
164 $this->app->__namespace__.'\\App\\Http\\Policies\\Policy'
165 );
166 }
167
168 /**
169 * Set the route policy
170 *
171 * @param mixed $handler
172 * @param string|null $method
173 * @return self
174 */
175 public function withPolicy($handler, $method = null)
176 {
177 if (is_array($handler = $method ? func_get_args() : $handler)) {
178 $handler = implode('@', $handler);
179 }
180
181 $this->staged['policy'] = $handler;
182
183 return $this;
184 }
185
186 /**
187 * Set the route before middleware
188 *
189 * @param array|string $middleware
190 * @return self
191 */
192 public function before(...$middleware)
193 {
194 return $this->middleware('before', ...$middleware);
195 }
196
197 /**
198 * Set the route after middleware
199 *
200 * @param array|string $middleware
201 * @return self
202 */
203 public function after(...$middleware)
204 {
205 return $this->middleware('after', ...$middleware);
206 }
207
208 /**
209 * Set the route middleware
210 *
211 * @param array|string $middleware
212 * @return self
213 */
214 public function middleware($type = 'before', ...$middleware)
215 {
216 if (is_array($middleware[0])) {
217 $middleware = reset($middleware);
218 }
219
220 $this->staged[$type] = array_merge(
221 $this->staged[$type], $middleware
222 );
223
224 return $this;
225 }
226
227 /**
228 * Merge whatever is currently staged into the given attributes
229 * and clear the staging area. Used by Group to absorb calls
230 * chained after group() into its own attributes.
231 *
232 * @param array $attributes
233 * @return array
234 */
235 public function absorbStaged(array $attributes)
236 {
237 return $this->mergeAttributes($attributes, $this->takeStaged());
238 }
239
240 /**
241 * Track a group so any instance kept alive past its statement
242 * can still be executed before routes are registered. A weak
243 * reference keeps the destructor firing at end of statement.
244 *
245 * @param Group $group
246 * @return void
247 */
248 public function trackGroup(Group $group)
249 {
250 if (class_exists(\WeakReference::class)) {
251 $this->pendingGroups[] = \WeakReference::create($group);
252 }
253 }
254
255 /**
256 * Execute any groups still pending execution because a
257 * reference to them was held beyond their statement.
258 *
259 * @return void
260 */
261 protected function executePendingGroups()
262 {
263 while ($this->pendingGroups) {
264 $reference = array_shift($this->pendingGroups);
265
266 if ($group = $reference->get()) {
267 $group->execute();
268 }
269 }
270 }
271
272 /**
273 * Execute a route group callback with the group's attributes
274 * as the active context. The previous context is restored
275 * afterwards, even if the callback throws, so execution is
276 * safe at any nesting depth and at any time.
277 *
278 * @param Closure $callback
279 * @param array $attributes
280 * @return void
281 */
282 public function executeGroupCallback($callback, array $attributes = [])
283 {
284 $previous = $this->context;
285
286 $this->context = $attributes + $this->newAttributes();
287
288 try {
289 $callback($this);
290 } finally {
291 $this->context = $previous;
292 }
293 }
294
295 /**
296 * Declare a GET route endpoint
297 * @param string $uri
298 * @param array|string|Closure $handler
299 * @return \FluentCommunity\Framework\Http\Route
300 */
301 public function get($uri, $handler)
302 {
303 $this->routes[] = $route = $this->newRoute(
304 $uri, $handler, 'GET'
305 );
306
307 return $route;
308 }
309
310 /**
311 * Declare a POST route endpoint
312 * @param string $uri
313 * @param array|string|Closure $handler
314 * @return \FluentCommunity\Framework\Http\Route
315 */
316 public function post($uri, $handler)
317 {
318 $this->routes[] = $route = $this->newRoute(
319 $uri, $handler, 'POST'
320 );
321
322 return $route;
323 }
324
325 /**
326 * Declare a PUT route endpoint
327 * @param string $uri
328 * @param array|string|Closure $handler
329 * @return \FluentCommunity\Framework\Http\Route
330 */
331 public function put($uri, $handler)
332 {
333 $this->routes[] = $route = $this->newRoute(
334 $uri, $handler, 'PUT'
335 );
336
337 return $route;
338 }
339
340 /**
341 * Declare a PATCH route endpoint
342 * @param string $uri
343 * @param array|string|Closure $handler
344 * @return \FluentCommunity\Framework\Http\Route
345 */
346 public function patch($uri, $handler)
347 {
348 $this->routes[] = $route = $this->newRoute(
349 $uri, $handler, 'PATCH'
350 );
351
352 return $route;
353 }
354
355 /**
356 * Declare a DELETE route endpoint
357 * @param string $uri
358 * @param array|string|Closure $handler
359 * @return \FluentCommunity\Framework\Http\Route
360 */
361 public function delete($uri, $handler)
362 {
363 $this->routes[] = $route = $this->newRoute(
364 $uri, $handler, 'DELETE'
365 );
366
367 return $route;
368 }
369
370 /**
371 * Declare a route endpoint that matches any HTTP Verb/Method
372 * @param string $uri
373 * @param array|string|Closure $handler
374 * @return \FluentCommunity\Framework\Http\Route
375 */
376 public function any($uri, $handler)
377 {
378 $this->routes[] = $route = $this->newRoute(
379 $uri, $handler, \WP_REST_Server::ALLMETHODS
380 );
381
382 return $route;
383 }
384
385 /**
386 * Create a new route instance
387 * @param string $uri
388 * @param string|Closure $handler
389 * @param string $method HTTP Method
390 * @return \FluentCommunity\Framework\Http\Route
391 */
392 protected function newRoute($uri, $handler, $method)
393 {
394 $attributes = $this->resolveAttributes();
395
396 $route = Route::create(
397 $this->app,
398 $this->getRestNamespace(),
399 $this->buildUriWithPrefix($uri, $attributes['prefix']),
400 $handler,
401 $method
402 );
403
404 if ($attributes['name']) {
405 $route->withName($attributes['name']);
406 }
407
408 if ($attributes['namespace']) {
409 $route->withNamespace($attributes['namespace']);
410 }
411
412 if ($attributes['policy']) {
413 $route->withPolicy($attributes['policy']);
414 }
415
416 if ($attributes['before']) {
417 $route->before($attributes['before']);
418 }
419
420 if ($attributes['after']) {
421 $route->after($attributes['after']);
422 }
423
424 if ($this->shouldOverride) {
425 $route->override();
426 }
427
428 return $route->preparefrontendHandlers();
429 }
430
431 /**
432 * An empty attribute set.
433 *
434 * @return array
435 */
436 protected function newAttributes()
437 {
438 return [
439 'name' => [],
440 'prefix' => [],
441 'namespace' => [],
442 'policy' => null,
443 'before' => [],
444 'after' => [],
445 ];
446 }
447
448 /**
449 * Return the staged attributes and reset the staging area.
450 *
451 * @return array
452 */
453 protected function takeStaged()
454 {
455 $staged = $this->staged;
456
457 $this->staged = $this->newAttributes();
458
459 return $staged;
460 }
461
462 /**
463 * The effective attributes for a route or group declared right
464 * now: the current group context merged with the staged
465 * attributes, which are claimed in the process.
466 *
467 * @return array
468 */
469 protected function resolveAttributes()
470 {
471 return $this->mergeAttributes($this->context, $this->takeStaged());
472 }
473
474 /**
475 * Merge attribute sets, outermost first. List attributes
476 * accumulate; the policy of the innermost set that declares
477 * one wins, so nested groups inherit their parent's policy
478 * unless they declare their own.
479 *
480 * @param array ...$sets
481 * @return array
482 */
483 protected function mergeAttributes(array ...$sets)
484 {
485 $merged = $this->newAttributes();
486
487 foreach ($sets as $set) {
488 foreach (['name', 'prefix', 'namespace', 'before', 'after'] as $key) {
489 if (!empty($set[$key])) {
490 $merged[$key] = array_merge($merged[$key], $set[$key]);
491 }
492 }
493
494 if (isset($set['policy'])) {
495 $merged['policy'] = $set['policy'];
496 }
497 }
498
499 return $merged;
500 }
501
502 /**
503 * Resolve the rest namespace for the plugin
504 *
505 * @return string
506 */
507 protected function getRestNamespace()
508 {
509 $version = $this->app->config->get('app.rest_version');
510
511 $namespace = trim(
512 $this->app->config->get('app.rest_namespace', ''), '/'
513 );
514
515 return "{$namespace}/{$version}";
516 }
517
518 /**
519 * Build the URI with the prefix
520 *
521 * @param string $uri
522 * @param array $prefixes
523 * @return string The URI
524 */
525 protected function buildUriWithPrefix($uri, array $prefixes = [])
526 {
527 $uri = trim($uri, '/');
528
529 $prefix = array_map(function($prefix) {
530 return trim($prefix, '/');
531 }, $prefixes);
532
533 $prefix = implode('/', $prefix);
534
535 return trim($prefix, '/') . '/' . trim($uri, '/');
536 }
537
538 /**
539 * Mark all routes created by this router to override existing ones.
540 *
541 * @return $this
542 */
543 public function overrideExisting()
544 {
545 $this->shouldOverride = true;
546
547 return $this;
548 }
549
550 /**
551 * Register all the routse in WordPress Rest Engine
552 *
553 * @return void
554 */
555 public function registerRoutes()
556 {
557 $this->executePendingGroups();
558
559 foreach ($this->getRoutes() as $route) {
560 $route->register();
561 }
562 }
563
564 /**
565 * Get all ther registered routes
566 * @return array
567 */
568 public function getRoutes()
569 {
570 return $this->routes;
571 }
572
573 /**
574 * Set a named route in the router.
575 *
576 * @param string $name
577 * @param Route $route
578 */
579 public function setNamedRoute($name, Route $route)
580 {
581 $this->namedRoutes[$name] = $route;
582
583 return $route;
584 }
585
586 /**
587 * Get a route by name.
588 *
589 * @param string $name
590 * @return Route|null
591 */
592 public function getByName($name)
593 {
594 return $this->namedRoutes[$name] ?? null;
595 }
596 }
597