PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.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
← All changes | vendor/wpfluent/framework/src/WPFluent/Http/Router.php +274 -89 1.0.952.11.0 View file →
@@ -1,8 +1,10 @@
1 1 <?php
2 2
3 3 namespace FluentCommunity\Framework\Http;
4 4
5 +use Closure;
6 +
5 7 class Router
6 8 {
7 9 /**
8 10 * Application Instance
@@ -8,47 +10,47 @@
8 10 * Application Instance
9 11 * @var \FluentCommunity\Framework\Foundation\Application
10 12 */
11 13 protected $app = null;
12 -
14 +
13 15 /**
14 - * Prefix for the route
16 + * Mapping of named routes.
15 17 * @var array
16 18 */
17 - protected $prefix = [];
18 -
19 + protected $namedRoutes = [];
20 +
19 21 /**
20 - * Controller/Handler namespace
22 + * Registered routes collection
21 23 * @var array
22 24 */
23 - protected $namespace = [];
25 + protected $routes = [];
24 26
25 27 /**
26 - * Registered routes collection
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.
27 31 * @var array
28 32 */
29 - protected $routes = [];
30 -
33 + protected $staged = [];
34 +
31 35 /**
32 - * Route policy handler to pass to the route
36 + * Effective attributes of the group whose callback is
37 + * currently executing. Empty outside any group.
33 38 * @var array
34 39 */
35 - protected $policyHandler = [];
40 + protected $context = [];
36 41
37 42 /**
38 - * Route middleware to pass to the route
39 - * @var array
43 + * Whether routes created by this router should override existing ones.
44 + * @var bool
40 45 */
41 - protected $middleware = [
42 - 'before' => [],
43 - 'after' => []
44 - ];
46 + protected $shouldOverride = false;
45 47
46 48 /**
47 - * Keep the track of number of group calls
48 - * @var integer
49 + * Weak references to groups pending execution.
50 + * @var array
49 51 */
50 - protected $groupCount = 0;
52 + protected $pendingGroups = [];
51 53
52 54 /**
53 55 * Construct the routet instance
54 56 * @param \FluentCommunity\Framework\Foundation\Application $app
@@ -55,25 +57,36 @@
55 57 */
56 58 public function __construct($app)
57 59 {
58 60 $this->app = $app;
61 + $this->staged = $this->newAttributes();
62 + $this->context = $this->newAttributes();
59 63 }
60 64
61 65 /**
62 - * Create a route group
63 - * @param array $attributes
64 - * @param \Closure|null $callback
65 - * @return null
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
66 77 */
67 - public function group($attributes = [], \Closure $callback = null)
78 + public function group($attributes = [], ?Closure $callback = null)
68 79 {
69 - $this->groupCount += 1;
70 -
71 - if ($attributes instanceof \Closure) {
80 + if ($attributes instanceof Closure) {
72 81 $callback = $attributes;
73 82 $attributes = [];
74 83 }
75 84
85 + if (isset($attributes['name'])) {
86 + $this->name($attributes['name']);
87 + }
88 +
76 89 if (isset($attributes['prefix'])) {
77 90 $this->prefix($attributes['prefix']);
78 91 }
79 92
@@ -86,46 +99,32 @@
86 99 }
87 100
88 101 if (isset($attributes['middleware'])) {
89 102 $middleware = $attributes['middleware'];
103 +
90 104 if (isset($middleware['before'])) {
91 105 $this->middleware('before', $middleware['before']);
92 - } elseif ($middleware['after']) {
93 - $this->middleware('after', $middleware['after']);
94 106 }
95 - }
96 107
97 - // If the current group doesn't have a policy handler
98 - // but the parent group has then bring it in this group.
99 - if (!isset($this->policyHandler[$this->groupCount])) {
100 - if (isset($this->policyHandler[$this->groupCount - 1])) {
101 - if ($policyHandler = $this->policyHandler[$this->groupCount - 1]) {
102 - $this->policyHandler[] = $policyHandler;
103 - }
108 + if (isset($middleware['after'])) {
109 + $this->middleware('after', $middleware['after']);
104 110 }
105 111 }
106 112
107 - // If the current group doesn't have a before middleware
108 - // but the parent group has then bring it in this group.
109 - if (!isset($this->middleware['before'][$this->groupCount])) {
110 - if (isset($this->middleware['before'][$this->groupCount - 1])) {
111 - if ($beforeMiddleware = $this->middleware['before'][$this->groupCount - 1]) {
112 - $this->middleware['before'][] = $beforeMiddleware;
113 - }
114 - }
115 - }
113 + return new Group($this, $callback, $this->resolveAttributes());
114 + }
116 115
117 - // If the current group doesn't have an after middleware
118 - // but the parent group has then bring it in this group.
119 - if (!isset($this->middleware['after'][$this->groupCount])) {
120 - if (isset($this->middleware['after'][$this->groupCount - 1])) {
121 - if ($afterMiddleware = $this->middleware['after'][$this->groupCount - 1]) {
122 - $this->middleware['after'][] = $afterMiddleware;
123 - }
124 - }
125 - }
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;
126 125
127 - return new Group($this, $callback);
126 + return $this;
128 127 }
129 128
130 129 /**
131 130 * Set the route prefix
@@ -134,9 +133,9 @@
134 133 * @return self
135 134 */
136 135 public function prefix($prefix)
137 136 {
138 - $this->prefix[] = $prefix;
137 + $this->staged['prefix'][] = $prefix;
139 138
140 139 return $this;
141 140 }
142 141
@@ -142,19 +141,32 @@
142 141
143 142 /**
144 143 * Set the namespace for the action/controller
145 144 *
146 - * @param string $namespace
145 + * @param string $ns
147 146 * @return self
148 147 */
149 148 public function namespace($ns)
150 149 {
151 - $this->namespace[] = $ns;
150 + $this->staged['namespace'][] = $ns;
152 151
153 152 return $this;
154 153 }
155 154
156 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 + /**
157 169 * Set the route policy
158 170 *
159 171 * @param mixed $handler
160 172 * @param string|null $method
@@ -165,9 +177,9 @@
165 177 if (is_array($handler = $method ? func_get_args() : $handler)) {
166 178 $handler = implode('@', $handler);
167 179 }
168 180
169 - $this->policyHandler[] = $handler;
181 + $this->staged['policy'] = $handler;
170 182
171 183 return $this;
172 184 }
173 185
@@ -204,10 +216,10 @@
204 216 if (is_array($middleware[0])) {
205 217 $middleware = reset($middleware);
206 218 }
207 219
208 - $this->middleware[$type] = array_merge(
209 - $this->middleware[$type], $middleware
220 + $this->staged[$type] = array_merge(
221 + $this->staged[$type], $middleware
210 222 );
211 223
212 224 return $this;
213 225 }
@@ -212,22 +224,73 @@
212 224 return $this;
213 225 }
214 226
215 227 /**
216 - * Execute the route group callback
217 - *
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 + *
218 278 * @param Closure $callback
219 - * @return null
279 + * @param array $attributes
280 + * @return void
220 281 */
221 - public function executeGroupCallback($callback)
282 + public function executeGroupCallback($callback, array $attributes = [])
222 283 {
223 - $callback($this);
224 - $this->groupCount -= 1;
225 - array_pop($this->prefix);
226 - array_pop($this->namespace);
227 - array_pop($this->middleware['before']);
228 - array_pop($this->middleware['after']);
229 - array_pop($this->policyHandler);
284 + $previous = $this->context;
285 +
286 + $this->context = $attributes + $this->newAttributes();
287 +
288 + try {
289 + $callback($this);
290 + } finally {
291 + $this->context = $previous;
292 + }
230 293 }
231 294
232 295 /**
233 296 * Declare a GET route endpoint
@@ -327,36 +390,117 @@
327 390 * @return \FluentCommunity\Framework\Http\Route
328 391 */
329 392 protected function newRoute($uri, $handler, $method)
330 393 {
394 + $attributes = $this->resolveAttributes();
395 +
331 396 $route = Route::create(
332 397 $this->app,
333 398 $this->getRestNamespace(),
334 - $this->buildUriWithPrefix($uri),
399 + $this->buildUriWithPrefix($uri, $attributes['prefix']),
335 400 $handler,
336 401 $method
337 402 );
338 403
339 - if ($this->namespace) {
340 - $route->withNamespace($this->namespace);
404 + if ($attributes['name']) {
405 + $route->withName($attributes['name']);
341 406 }
342 407
343 - if ($this->policyHandler) {
344 - $route->withPolicy(end($this->policyHandler));
408 + if ($attributes['namespace']) {
409 + $route->withNamespace($attributes['namespace']);
345 410 }
346 411
347 - if ($this->middleware['before']) {
348 - $route->before($this->middleware['before']);
412 + if ($attributes['policy']) {
413 + $route->withPolicy($attributes['policy']);
349 414 }
350 415
351 - if ($this->middleware['after']) {
352 - $route->after($this->middleware['after']);
416 + if ($attributes['before']) {
417 + $route->before($attributes['before']);
353 418 }
354 419
355 - return $route;
420 + if ($attributes['after']) {
421 + $route->after($attributes['after']);
422 + }
423 +
424 + if ($this->shouldOverride) {
425 + $route->override();
426 + }
427 +
428 + return $route->preparefrontendHandlers();
356 429 }
357 430
358 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 + /**
359 503 * Resolve the rest namespace for the plugin
360 504 *
361 505 * @return string
362 506 */
@@ -374,17 +518,18 @@
374 518 /**
375 519 * Build the URI with the prefix
376 520 *
377 521 * @param string $uri
522 + * @param array $prefixes
378 523 * @return string The URI
379 524 */
380 - protected function buildUriWithPrefix($uri)
525 + protected function buildUriWithPrefix($uri, array $prefixes = [])
381 526 {
382 527 $uri = trim($uri, '/');
383 528
384 529 $prefix = array_map(function($prefix) {
385 530 return trim($prefix, '/');
386 - }, $this->prefix);
531 + }, $prefixes);
387 532
388 533 $prefix = implode('/', $prefix);
389 534
390 535 return trim($prefix, '/') . '/' . trim($uri, '/');
@@ -390,15 +535,31 @@
390 535 return trim($prefix, '/') . '/' . trim($uri, '/');
391 536 }
392 537
393 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 + /**
394 551 * Register all the routse in WordPress Rest Engine
395 - *
396 - * @return null
552 + *
553 + * @return void
397 554 */
398 555 public function registerRoutes()
399 556 {
400 - foreach ($this->routes as $route) $route->register();
557 + $this->executePendingGroups();
558 +
559 + foreach ($this->getRoutes() as $route) {
560 + $route->register();
561 + }
401 562 }
402 563
403 564 /**
404 565 * Get all ther registered routes
@@ -406,6 +567,30 @@
406 567 */
407 568 public function getRoutes()
408 569 {
409 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;
410 595 }
411 596 }