PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.21
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.21
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Http / Route.php

Route.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.21, at vendor/wpfluent/framework/src/WPFluent/Http/Route.php

987 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\Framework\Http;
4
5 use Closure;
6 use Exception;
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use BadMethodCallException;
10 use InvalidArgumentException;
11 use FluentBooking\Framework\Support\Arr;
12 use FluentBooking\Framework\Support\Pipeline;
13 use FluentBooking\Framework\Validator\ValidationException;
14 use FluentBooking\Framework\Database\Orm\ModelNotFoundException;
15 use FluentBooking\Framework\Response\Response as WPFluentResponse;
16
17 class Route
18 {
19 use SubstituteRouteParametersTrait;
20
21 /**
22 * Application Instance
23 * @var \FluentBooking\Framework\Foundation\Application
24 */
25 protected $app = null;
26
27 /**
28 * Rest namespace from config
29 * @var string
30 */
31 protected $restNamespace = null;
32
33 /**
34 * Full URI
35 * @var string
36 */
37 protected $uri = null;
38
39 /**
40 * Compiled rest endpoint
41 * @var string
42 */
43 protected $compiled = null;
44
45 /**
46 * Route meta data
47 * @var array
48 */
49 protected $meta = [];
50
51 /**
52 * Rest Handler/Callback before parsing
53 * @var string
54 */
55 protected $handler = null;
56
57 /**
58 * Rest Handler/Callback after parsing
59 * @var callable|string
60 */
61 protected $action = null;
62
63 /**
64 * Rest route action info after parsing
65 * @var array
66 */
67 protected $actionInfo = [];
68
69 /**
70 * Policy Handler/Callback after parsing
71 * @var string
72 */
73 protected $permissionHandler = [];
74
75 /**
76 * HTTP Methods
77 * @var string
78 */
79 protected $method = null;
80
81 /**
82 * Rest options
83 * @var array
84 */
85 protected $options = [];
86
87 /**
88 * Route where constraints
89 * @var array
90 */
91 protected $wheres = [];
92
93 /**
94 * Rest namespace
95 * @var string
96 */
97 protected $namespace = null;
98
99 /**
100 * Policy Handler/Callback after parsing
101 * @var callable|string
102 */
103 protected $policyHandler = null;
104
105 /**
106 * Route Middleware
107 * @var array
108 */
109 protected $middleware = [
110 'before' => [],
111 'after' => []
112 ];
113
114 /**
115 * Skips middlewar if true
116 *
117 * @var boolean
118 */
119 protected $skipMiddleware = false;
120
121 /**
122 * Predefined Regex foe where constraints
123 * @var array
124 */
125 protected $predefinedNamedRegx = [
126 'int' => '[0-9]+',
127 'alpha' => '[a-zA-Z]+',
128 'alpha_num' => '[a-zA-Z0-9]+',
129 'alpha_num_dash' => '[a-zA-Z0-9-_]+'
130 ];
131
132 /**
133 * Route parameters
134 * @var null|array
135 */
136 protected $parameters = null;
137
138 /**
139 * Route substituted parameters
140 *
141 * @var null|array
142 */
143 protected $substitutedParameters = [];
144
145 /**
146 * Construct the route instance
147 *
148 * @param \FluentBooking\Framework\Foundation\Application $app
149 * @param string $restNamespace
150 * @param string $uri
151 * @param string $handler
152 * @param string $method
153 */
154 public function __construct($app, $restNamespace, $uri, $handler, $method)
155 {
156 $this->app = $app;
157 $this->restNamespace = $restNamespace;
158 $this->uri = $uri;
159 $this->handler = $handler;
160 $this->method = $method;
161 }
162
163 /**
164 * Alternative constructor
165 *
166 * @param \FluentBooking\Framework\Foundation\Application $app
167 * @param string $restNamespace
168 * @param string $uri
169 * @param string $handler
170 * @param string $method
171 * @return self
172 */
173 public static function create($app, $namespace, $uri, $handler, $method)
174 {
175 return new static($app, $namespace, $uri, $handler, $method);
176 }
177
178 /**
179 * Set route meta
180 *
181 * @param string $key
182 * @param mixed $value
183 * @return self
184 */
185 public function meta($key, $value = null)
186 {
187 $meta = is_array($key) ? $key : [$key => $value];
188
189 $this->meta = array_merge($this->meta, $meta);
190
191 return $this;
192 }
193
194 /**
195 * Get route meta
196 *
197 * @param string $key
198 * @return mixed
199 */
200 public function getMeta($key = '')
201 {
202 if (isset($this->meta[$key])) {
203 return $this->meta[$key];
204 }
205
206 return $this->meta;
207 }
208
209 /**
210 * Get route options
211 *
212 * @return mixed
213 */
214 public function getOptions()
215 {
216 return $this->getOption();
217 }
218
219 /**
220 * Get route options
221 *
222 * @param string $key
223 * @return mixed
224 */
225 public function getOption($key = null)
226 {
227 return $key ? $this->options[$key] : $this->options;
228 }
229
230 /**
231 * Get route action information
232 * @param string $key
233 * @return mixed
234 */
235 public function getAction($key = '')
236 {
237 if ($key && array_key_exists($key, $this->actionInfo)) {
238 return $this->actionInfo[$key];
239 }
240
241 return $this->actionInfo;
242 }
243
244 /**
245 * Set a where constrain into the route
246 *
247 * @param string $identifier
248 * @param string $value
249 * @return self
250 */
251 public function where($identifier, $value = null)
252 {
253 if (!is_null($value)) {
254 $this->wheres[$identifier] = $this->getValue($value);
255 } else {
256 foreach ($identifier as $key => $value) {
257 $this->wheres[$key] = $this->getValue($value);
258 }
259 }
260
261 return $this;
262 }
263
264 /**
265 * Add an integer type route constraint
266 *
267 * @param string $identifiers
268 * @return self
269 */
270 public function int($identifiers)
271 {
272 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
273
274 foreach ($identifiers as $identifier) {
275 $this->wheres[$identifier] = '[0-9]+';
276 }
277
278 return $this;
279 }
280
281 /**
282 * Add an alpha type route constraint
283 *
284 * @param string $identifiers
285 * @return self
286 */
287 public function alpha($identifiers)
288 {
289 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
290
291 foreach ($identifiers as $identifier) {
292 $this->wheres[$identifier] = '[a-zA-Z]+';
293 }
294
295 return $this;
296 }
297
298 /**
299 * Add an alphanum type route constraint
300 *
301 * @param string $identifiers
302 * @return self
303 */
304 public function alphaNum($identifiers)
305 {
306 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
307
308 foreach ($identifiers as $identifier) {
309 $this->wheres[$identifier] = '[a-zA-Z0-9]+';
310 }
311
312 return $this;
313 }
314
315 /**
316 * Add an alphanumdash type route constraint
317 *
318 * @param string $identifiers
319 * @return self
320 */
321 public function alphaNumDash($identifiers)
322 {
323 $identifiers = is_array($identifiers) ? $identifiers : func_get_args();
324
325 foreach ($identifiers as $identifier) {
326 $this->wheres[$identifier] = '[a-zA-Z0-9-_]+';
327 }
328
329 return $this;
330 }
331
332 /**
333 * Set the route before middleware
334 *
335 * @param array|string $middleware
336 * @return self
337 */
338 public function before(...$middleware)
339 {
340 return $this->middleware('before', ...$middleware);
341 }
342
343 /**
344 * Set the route after middleware
345 *
346 * @param array|string $middleware
347 * @return self
348 */
349 public function after(...$middleware)
350 {
351 return $this->middleware('after', ...$middleware);
352 }
353
354 /**
355 * Set the route middleware
356 * @param array $middleware
357 * @return self
358 */
359 public function middleware($type = 'before', ...$middleware)
360 {
361 if (is_array($middleware[0])) {
362 $middleware = reset($middleware);
363 }
364
365 $this->middleware[$type] = array_merge(
366 $this->middleware[$type], $middleware
367 );
368
369 return $this;
370 }
371
372 /**
373 * Set the route policy
374 *
375 * @param mixed $handler
376 * @param string|null $method
377 * @return self
378 */
379 public function withPolicy($handler, $method = null)
380 {
381 if (is_array($handler = $method ? func_get_args() : $handler)) {
382 $handler = implode('@', $handler);
383 }
384
385 $this->policyHandler = $handler;
386
387 if (is_string($handler) && !$this->app->hasNamespace($handler)) {
388 $this->setPolicyHandlerWithNamespace(
389 debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4)
390 );
391 }
392
393 return $this;
394 }
395
396 /**
397 * Resolve and set policy with namespace for add-ons
398 *
399 * @param null
400 */
401 protected function setPolicyHandlerWithNamespace($backTrace)
402 {
403 $last = end($backTrace);
404
405 if (!isset($last['class'])) return;
406
407 $class = $last['class'];
408
409 $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\'));
410
411 $calledClassNamespace = substr($class, 0, strpos($class, '\\'));
412
413 if ($namespace != $calledClassNamespace) {
414 $ns = $calledClassNamespace . '\\App\\Http\\Policies\\';
415 $this->policyHandler = $ns . $this->policyHandler;
416 }
417 }
418
419 /**
420 * Set the namespace for controller/action
421 * @param string $ns
422 * @return null
423 */
424 public function withNamespace($ns)
425 {
426 $this->namespace = implode('\\', $ns);
427 }
428
429 /**
430 * Register the rest endpoint
431 *
432 * @return null
433 */
434 public function register()
435 {
436 $this->setOptions();
437
438 $uri = '/' . trim($this->compileRoute($this->uri), '/');
439
440 return register_rest_route(
441 $this->restNamespace, $uri, $this->getOptions()
442 );
443 }
444
445 /**
446 * Set route options
447 *
448 * @return null
449 */
450 protected function setOptions()
451 {
452 $this->options = [
453 'args' => [],
454 'methods' => $this->method,
455 'callback' => [$this, 'callback'],
456 'permission_callback' => [$this, 'permissionCallback']
457 ];
458 }
459
460 /**
461 * Get item from predefined regex
462 * @param string $value
463 * @return string
464 */
465 protected function getValue($value)
466 {
467 if (array_key_exists($value, $this->predefinedNamedRegx)) {
468 return $this->predefinedNamedRegx[$value];
469 }
470
471 return $value;
472 }
473
474 /**
475 * Compikle the rest route to regex
476 *
477 * @param string $uri
478 * @return string compiled rest endpoint
479 */
480 protected function compileRoute($uri)
481 {
482 $params = [];
483
484 $compiledUri = preg_replace_callback('#/{(.*?)}#', function($match) use (&$params, $uri) {
485 // Default regx
486 $regx = '[^\s(?!/)]+';
487
488 $param = trim($match[1]);
489
490 if ($isOptional = strpos($param, '?')) {
491 $param = trim($param, '?');
492 }
493
494 if (in_array($param, $params)) {
495 throw new InvalidArgumentException(
496 "Duplicate parameter name '{$param}' found in {$uri}.", 500
497 );
498 }
499
500 $params[] = $param;
501
502 if (isset($this->wheres[$param])) {
503 $regx = $this->wheres[$param];
504 }
505
506 $pattern = "/(?P<" . $param . ">" . $regx . ")";
507
508 if ($isOptional) {
509 $pattern = "(?:" . $pattern . ")?";
510 }
511
512 $this->options['args'][$param]['required'] = !$isOptional;
513
514 return $pattern;
515
516 }, $uri);
517
518 return $this->compiled = $compiledUri;
519 }
520
521 /**
522 * Route handler
523 *
524 * @return mixed
525 */
526 public function callback()
527 {
528 try {
529
530 $response = $this->app->call(
531 $this->action, $this->getControllerParameters()
532 );
533
534 if ($response instanceof WPFluentResponse) {
535 $response = $response->toArray();
536 }
537
538 if (!($response instanceof WP_REST_Response)) {
539 if (is_wp_error($response)) {
540 $response = $this->app->response->wpErrorToResponse($response);
541 } else {
542 $response = $this->app->response->sendSuccess($response);
543 }
544 }
545
546 if (!$this->skipMiddleware) {
547 $response = $this->app->make(Pipeline::class)
548 ->send($response)
549 ->through($this->collectMiddleWare('after'))
550 ->then(function($response) {
551 if (!$response instanceof WP_REST_Response) {
552 $response = new WP_REST_Response($response);
553 }
554 return $response;
555 });
556
557 if (!$response) {
558 $response = $this->app->request->abort();
559 }
560 }
561
562 return $response;
563
564 } catch (ValidationException $e) {
565 return $this->app->response->sendError(
566 $e->errors(), $e->getCode()
567 );
568 } catch (ModelNotFoundException $e) {
569 return $this->app->response->sendError([
570 'message' => $e->getMessage()
571 ], 404);
572 } catch (Exception $e) {
573 return $this->app->response->sendError([
574 'message' => $e->getMessage()
575 ], $e->getCode() ?: 500);
576 }
577 }
578
579 /**
580 * Permission callback for route
581 * @param \WP_REST_Request $wpRestRequest
582 * @return mixed
583 */
584 public function permissionCallback($wpRestRequest)
585 {
586 try {
587
588 $this->app->instance('route', $this);
589
590 if (!$this->app->bound('wprestrequest')) {
591 $this->app->instance('wprestrequest', $wpRestRequest);
592 $this->app->request->mergeInputsFromRestRequest($wpRestRequest);
593
594 if (method_exists($this, 'prepareCallbacks')) {
595 $this->prepareCallbacks($this->app->request);
596 }
597 }
598
599 $response = $this->app->make(Pipeline::class)
600 ->send($this->app->request)
601 ->through($this->collectMiddleWare('before'))
602 ->then(function($request) {
603 return $this->dispatchPermissionHandler();
604 });
605
606 if (is_wp_error($response)) {
607 throw new Exception(
608 $response->get_error_message(),
609 is_int($code = $response->get_error_code()) ? $code : 403
610 );
611 }
612
613 if ($response instanceof WP_REST_Response) {
614 $data = $response->get_data();
615
616 throw new Exception(
617 $data['message'] ?? $response->get_status(), $response->get_status()
618 );
619 }
620
621 return $response;
622
623 } catch (Exception $e) {
624 $this->skipMiddleware = true;
625 $this->action = function() use ($e) {
626 return $this->app->response->sendError(
627 ['message' => $e->getMessage()], $e->getCode()
628 );
629 };
630 }
631 }
632
633 /**
634 * Dispatches the permission handler
635 *
636 * @return bool|null
637 */
638 protected function dispatchPermissionHandler()
639 {
640 if ($this->permissionHandler) {
641 return $this->app->call(
642 $this->permissionHandler,
643 $this->getControllerParameters()
644 );
645 }
646 }
647
648 /**
649 * Gether route params after substituted the params
650 *
651 * @return array
652 */
653 protected function getControllerParameters()
654 {
655 $routeParameters = [];
656
657 if (!$this->substitutedParameters) {
658 if ($routeParameters = $this->getParameter()) {
659 $routeParameters = $this->SubstituteParameters($routeParameters);
660 }
661 } else {
662 $routeParameters = $this->substitutedParameters;
663 }
664
665 return $routeParameters;
666 }
667
668 /**
669 * Added the ability to add middleware so we can intercept
670 * the request without modifying the source code again
671 * and again. The middleware class will implement
672 * the handle method as given below:
673 *
674 * public function handle($request, $next)
675 *
676 * And must return $next($request) to handle the request.
677 * Otherwise return nothing to abort the request.
678 * Optionally, you may call the abort method:
679 * return $request->abort(code, message);
680 *
681 * @param string $type
682 * @return array
683 */
684 protected function collectMiddleWare($type = 'before')
685 {
686 $middleware = $this->app['config']->get('middleware', []);
687
688 $callableMiddleware = Arr::get($middleware, "global.{$type}", []);
689
690 $routeArray = [];
691
692 if (isset($middleware['route'])) {
693 $routeArray = $middleware['route'];
694 if (isset($routeArray[$type])) {
695 $routeArray = $routeArray[$type];
696 }
697 }
698
699 foreach ($this->middleware[$type] as $routeMiddleware) {
700
701 if (is_object($routeMiddleware)) {
702 $handler = $routeMiddleware;
703 } else {
704 $pieces = explode(':', $routeMiddleware);
705 $handler = Arr::get($routeArray, $key = reset($pieces));
706
707 if (isset($pieces[1])) {
708 $handler = $this->resolveMiddleware($handler, $pieces);
709 }
710 }
711
712 if (isset($handler)) {
713 $this->addMiddlewareInTheStack($callableMiddleware, $handler);
714 } else {
715 if (isset($key)) {
716 $mpath = 'config.middleware.route.' . $type;
717 $msg = "No middleware is assigned for the key: {$key} in {$mpath} array.";
718 } else {
719 $msg = "Could't resolve middleware.";
720 }
721
722 throw new InvalidArgumentException($msg);
723 }
724 }
725
726 return $callableMiddleware;
727 }
728
729 /**
730 * Resolve the middleware
731 *
732 * @param mixed $handler
733 * @param aray $pieces
734 * @return object
735 */
736 protected function resolveMiddleware($handler, $pieces)
737 {
738 if (is_object($handler)) {
739 $handler = $this->wrapMiddleware($handler, $pieces);
740 } elseif (is_string($handler)) {
741 $handler = $handler . ':' . str_replace(' ', '', end($pieces));
742 }
743
744 return $handler;
745 }
746
747 /**
748 * Create a class to wrap the middleware
749 *
750 * @param mixed $handler
751 * @param aray $pieces
752 * @return object
753 */
754 protected function wrapMiddleware($handler, $pieces)
755 {
756 $params = str_replace(' ', '', end($pieces));
757
758 $params = explode(',', $params);
759
760 return new class ($handler, $params) {
761 protected $handler, $params = null;
762 public function __construct($handler, $params) {
763 $this->handler = $handler;
764 $this->params = $params;
765 }
766 public function handle($target, $next) {
767 if (is_callable($this->handler)) {
768 return ($this->handler)($target, $next, ...$this->params);
769 } else {
770 return $this->handler->handle(
771 $target, $next, ...$this->params
772 );
773 }
774 }
775 };
776 }
777
778 /**
779 * Add the middleware in the stack
780 *
781 * @param array &$stack All callable middleware for the route
782 * @param null
783 */
784 protected function addMiddlewareInTheStack(&$stack, $middleware)
785 {
786 if (!in_array($middleware, $stack)) {
787 $stack[] = $middleware;
788 }
789 }
790
791 /**
792 * Resolve the policy handler
793 *
794 * @param string $policyHandler
795 * @return mixed
796 */
797 protected function getPolicyHandler($policyHandler)
798 {
799 if (!$policyHandler) {
800 return [$this, 'defaultPolicyHandler'];
801 }
802
803 if (is_callable($policyHandler)) {
804 return $policyHandler;
805 }
806
807 if (is_string($policyHandler)) {
808
809 if (function_exists($policyHandler)) {
810 return $policyHandler;
811 }
812
813 $policyHandlerFunction = substr($policyHandler, strrpos($policyHandler, '\\') + 1);
814
815 if (function_exists($policyHandlerFunction)) {
816 return $policyHandlerFunction;
817 }
818 }
819
820 if ($this->isPolicyHandlerParseable($policyHandler)) {
821 return $policyHandler;
822 }
823
824 if (is_string($policyHandler) && $this->handler instanceof Closure) {
825
826 if (class_exists($policyHandler)) {
827
828 $reflection = new \ReflectionClass($policyHandler);
829
830 if ($reflection->hasMethod('verifyRequest')) {
831
832 $policyHandler = $policyHandler . '@' . 'verifyRequest';
833
834 return $policyHandler;
835 }
836 } elseif (function_exists($policyHandler)) {
837 return $policyHandler;
838 }
839
840 throw new InvalidArgumentException(
841 'Explicit policy handler is required while using a closure as route callback.'
842 );
843 }
844
845 if ($policyHandler && !function_exists($policyHandler)) {
846 if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
847 list($_, $method) = explode('@', $this->handler);
848 $policyHandler = $policyHandler . '@' . $method;
849 } else if (is_array($this->handler)) {
850 $policyHandler = $policyHandler . '@' . $this->handler[1];
851 }
852 }
853
854 return $policyHandler ?: [$this, 'defaultPolicyHandler'];
855 }
856
857 protected function isPolicyHandlerParseable($policyHandler)
858 {
859 return (strpos($policyHandler, '@') === true
860 || strpos($policyHandler, '::') === true);
861 }
862
863 /**
864 * Default/Fallback policy handler for the route
865 *
866 * @return bool
867 */
868 public function defaultPolicyHandler()
869 {
870 return true;
871 }
872
873 /**
874 * Parse the rest and permission/policy handlers
875 *
876 * @param \WP_REST_Request $request
877 * @return null
878 * @throws \BadMethodCallException
879 */
880 public function prepareCallbacks($request)
881 {
882 $handler = $this->app->parseRestHandler(
883 $this->handler, $this->namespace
884 );
885
886 if ($handler instanceof Closure) {
887 $action = 'Closure';
888 $controller = null;
889 } else {
890 $handler = trim($handler, '\\');
891 $action = explode('@', $handler);
892 $pieces = explode('\\', $action[0]);
893 $controller = end($pieces);
894 }
895
896 try {
897 $policyHandler = $this->app->parsePolicyHandler(
898 $this->getPolicyHandler($this->policyHandler)
899 );
900
901 if ($policyHandler) {
902 $this->permissionHandler = $policyHandler;
903
904 // Adjust policy handler if the method was explicitly given
905 if (is_string($this->policyHandler)) {
906 if (is_array($policyHandler) && isset($policyHandler[1])) {
907 if ($pieces = explode('@', $this->policyHandler)) {
908 if (isset($pieces[1])) {
909 $this->permissionHandler[1] = $pieces[1];
910 }
911 }
912 }
913 }
914
915 if (!is_callable($this->permissionHandler)) {
916 throw new Exception;
917 }
918 }
919
920 } catch (Exception $e) {
921 $pHandler = $this->policyHandler;
922 if (is_array($this->permissionHandler) && $this->permissionHandler) {
923 $pHandler = is_object($this->permissionHandler[0]) ?
924 get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] :
925 $this->permissionHandler[0] . ':' . $this->permissionHandler[1];
926 }
927
928 throw new BadMethodCallException(
929 "The permission callback {$pHandler} is invalid or not callable."
930 );
931 }
932
933 if (is_array($policyHandler)) {
934 $policyHandler[0] = get_class($policyHandler[0]);
935 }
936
937 $this->actionInfo = [
938 'handler' => is_object($handler) ? $action : $handler,
939 'controller' => $controller,
940 'method' => is_array($action) ? $action[1] : null,
941 'path' => $this->uri,
942 'http_method' => $request->get_method(),
943 'full_uri' => $request->get_route(),
944 'permission_callback' => $policyHandler,
945 'compiled_url' => $this->compiled
946 ];
947
948
949 $this->action = $handler;
950
951 if ($routeParameters = $this->getParameter()) {
952 $this->substitutedParameters = $this->SubstituteParameters(
953 $routeParameters
954 );
955 }
956
957
958 return $this->action;
959 }
960
961 /**
962 * Get route one or more parameters
963 * @param string $key
964 *
965 * @return mixed
966 */
967 public function getParameter($key = null)
968 {
969 if (is_null($this->parameters)) {
970 $this->parameters = $this->app->request->get_url_params();
971 }
972
973 return $key ? $this->parameters[$key] : $this->parameters;
974 }
975
976 /**
977 * Dynamically access a route parameter.
978 *
979 * @param string $key
980 * @return mixed
981 */
982 public function __get($key)
983 {
984 return $this->getParameter($key);
985 }
986 }
987