PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.0.13
Kirki – Freeform Page Builder, Website Builder & Customizer v6.0.13
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / libraries / framework / Route.php
kirki / libraries / framework Last commit date
Collections 3 weeks ago Concerns 3 weeks ago Console 3 weeks ago Constants 3 weeks ago Contracts 3 weeks ago Database 3 weeks ago Discovery 3 weeks ago Exceptions 3 weeks ago Filesystem 3 weeks ago Http 3 weeks ago Managers 3 weeks ago Middlewares 3 weeks ago Polyfill 3 weeks ago Supports 3 weeks ago Validation 3 weeks ago Wordpress 3 weeks ago ApiExceptionHandler.php 3 weeks ago Application.php 3 weeks ago Container.php 3 weeks ago CoreServiceProvider.php 3 weeks ago DTO.php 3 weeks ago Facade.php 3 weeks ago Listener.php 3 weeks ago Resource.php 3 weeks ago Route.php 3 weeks ago Sanitizer.php 3 weeks ago ServiceProvider.php 3 weeks ago helpers.php 3 weeks ago
Route.php
926 lines
1 <?php
2
3 /**
4 * Fluent REST API route registrar for WordPress with middleware, grouping, and controller binding.
5 * Resolves controller actions via reflection and dependency injection from the container.
6 * Registers routes on rest_api_init through RegisterRestApi.
7 *
8 * @package Framework
9 * @since 1.0.0
10 */
11 namespace Kirki\Framework;
12
13 \defined('ABSPATH') || exit;
14 use Closure;
15 use Kirki\Framework\Contracts\Request as RequestContract;
16 use Kirki\Framework\Database\Query\Model;
17 use Exception;
18 use Kirki\Framework\Collections\Collection;
19 use Kirki\Framework\Contracts\Middleware;
20 use Kirki\Framework\Exceptions\AuthorizationException;
21 use Kirki\Framework\Exceptions\InvalidRoutActionException;
22 use Kirki\Framework\Exceptions\ModelNotFoundException;
23 use Kirki\Framework\Http\Request;
24 use InvalidArgumentException;
25 use ReflectionClass;
26 use ReflectionFunction;
27 use ReflectionMethod;
28 use ReflectionNamedType;
29 use WP_Error;
30 use WP_REST_Request;
31 use function Kirki\Framework\app;
32 use function Kirki\Framework\Polyfill\array_first;
33 use function Kirki\Framework\Polyfill\array_last;
34 class Route
35 {
36 /**
37 * REST API namespace.
38 *
39 * @var string
40 *
41 * @since 1.0.0
42 */
43 protected static $namespace = '';
44 /**
45 * Array of registered routes.
46 *
47 * @var array
48 *
49 * @since 1.0.0
50 */
51 protected static $routes = [];
52 /**
53 * Group stack to hold the group options.
54 *
55 * @var array
56 *
57 * @since 1.0.0
58 */
59 protected static $group_stack = [];
60 /**
61 * HTTP method for the route.
62 *
63 * @var string
64 *
65 * @since 1.0.0
66 */
67 protected $method;
68 /**
69 * The endpoint path for the route.
70 *
71 * @var string
72 *
73 * @since 1.0.0
74 */
75 protected $endpoint;
76 /**
77 * Controller class and method for handling the route.
78 *
79 * @var array
80 *
81 * @since 1.0.0
82 */
83 protected $action;
84 /**
85 * Array of middleware classes.
86 *
87 * @var array
88 *
89 * @since 1.0.0
90 */
91 protected $middlewares = [];
92 /**
93 * Regex patterns.
94 *
95 * @var array
96 *
97 * @since 1.0.0
98 */
99 protected $patterns = [];
100 /**
101 * Array of class instances.
102 *
103 * @var array
104 *
105 * @since 1.0.0
106 */
107 protected static $instances = [];
108 /**
109 * The resolved request.
110 *
111 * @var Request
112 *
113 * @since 1.0.0
114 */
115 protected $resolved_request;
116 /**
117 * Set the API namespace for all registered routes.
118 *
119 * @param string $namespace The namespace for REST API routes.
120 *
121 * @return void
122 *
123 * @since 1.0.0
124 */
125 public static function set_namespace(string $namespace)
126 {
127 static::$namespace = $namespace;
128 }
129 /**
130 * Get the API namespace.
131 *
132 * @return string
133 *
134 * @since 1.0.0
135 */
136 public static function get_namespace()
137 {
138 return static::$namespace;
139 }
140 /**
141 * Get the URL for a specific route.
142 *
143 * @param string $path The route path.
144 *
145 * @return string The URL for the route.
146 *
147 * @since 1.0.0
148 */
149 public static function url(string $path)
150 {
151 return rest_url('/' . static::$namespace . '/' . $path);
152 }
153 /**
154 * Attach middleware to the current route.
155 *
156 * @param string|array $middleware The fully qualified class name of the middleware.
157 *
158 * @return $this
159 *
160 * @since 1.0.0
161 */
162 public function middleware($middleware)
163 {
164 if (\is_array($middleware)) {
165 $this->middlewares = \array_merge($this->middlewares, $middleware);
166 return $this;
167 }
168 $this->middlewares[] = $middleware;
169 return $this;
170 }
171 /**
172 * Set a regex pattern for the specific route param.
173 *
174 * @param string $name The name.
175 * @param string $regex The regex.
176 *
177 * @return static
178 *
179 * @since 1.0.0
180 */
181 public function where(string $name, string $regex)
182 {
183 $this->patterns[$name] = $regex;
184 return $this;
185 }
186 /**
187 * Get the endpoint in proper format that register_rest_route() expects.
188 *
189 * @return void
190 *
191 * @since 1.0.0
192 */
193 protected function get_formatted_endpoint()
194 {
195 return \preg_replace_callback('/\\{(\\w+)\\}/', function ($matches) {
196 $param = $matches[1];
197 $pattern = isset($this->patterns[$param]) ? $this->patterns[$param] : '[^/]+';
198 return '(?P<' . $param . '>' . $pattern . ')';
199 }, $this->endpoint);
200 }
201 /**
202 * Register a GET route.
203 *
204 * @param string $endpoint The route endpoint.
205 * @param array|Closure $action The controller and method to handle the route.
206 *
207 * @return static
208 *
209 * @since 1.0.0
210 */
211 public static function get(string $endpoint, $action)
212 {
213 $instance = new static();
214 $instance->method = 'get';
215 $instance->endpoint = $endpoint;
216 $instance->action = $action;
217 $instance->apply_group_options();
218 static::$routes[] = $instance;
219 return $instance;
220 }
221 /**
222 * Register a POST route.
223 *
224 * @param string $endpoint The route endpoint.
225 * @param array|Closure $action The controller and method to handle the route.
226 *
227 * @return static
228 *
229 * @since 1.0.0
230 */
231 public static function post(string $endpoint, $action)
232 {
233 $instance = new static();
234 $instance->method = 'post';
235 $instance->endpoint = $endpoint;
236 $instance->action = $action;
237 $instance->apply_group_options();
238 static::$routes[] = $instance;
239 return $instance;
240 }
241 /**
242 * Register a PUT route.
243 *
244 * @param string $endpoint The route endpoint.
245 * @param array|Closure $action The controller and method to handle the route.
246 *
247 * @return static
248 *
249 * @since 1.0.0
250 */
251 public static function put(string $endpoint, $action)
252 {
253 $instance = new static();
254 $instance->method = 'put';
255 $instance->endpoint = $endpoint;
256 $instance->action = $action;
257 $instance->apply_group_options();
258 static::$routes[] = $instance;
259 return $instance;
260 }
261 /**
262 * Register a PATCH route.
263 *
264 * @param string $endpoint The route endpoint.
265 * @param array|Closure $action The controller and method to handle the route.
266 *
267 * @return static
268 *
269 * @since 1.0.0
270 */
271 public static function patch(string $endpoint, $action)
272 {
273 $instance = new static();
274 $instance->method = 'patch';
275 $instance->endpoint = $endpoint;
276 $instance->action = $action;
277 $instance->apply_group_options();
278 static::$routes[] = $instance;
279 return $instance;
280 }
281 /**
282 * Register a DELETE route.
283 *
284 * @param string $endpoint The route endpoint.
285 * @param array|Closure $action The controller and method to handle the route.
286 *
287 * @return static
288 *
289 * @since 1.0.0
290 */
291 public static function delete(string $endpoint, $action)
292 {
293 $instance = new static();
294 $instance->method = 'delete';
295 $instance->endpoint = $endpoint;
296 $instance->action = $action;
297 $instance->apply_group_options();
298 static::$routes[] = $instance;
299 return $instance;
300 }
301 /**
302 * Register a group of routes with shared options.
303 *
304 * This method allows grouping routes under common configuration options
305 * like middleware, or prefix. The closure receives the context
306 * of the group and defines the routes within it.
307 *
308 * @param array $options The shared configuration options for the group.
309 * @param \Closure $closure The callback that defines the grouped routes.
310 *
311 * @return void
312 *
313 * @since 1.0.0
314 */
315 public static function group(array $options, Closure $closure)
316 {
317 static::$group_stack[] = $options;
318 $closure();
319 \array_pop(static::$group_stack);
320 }
321 /**
322 * Get all registered routes.
323 *
324 * @return array
325 *
326 * @since 1.0.0
327 */
328 public static function get_routes()
329 {
330 return static::$routes;
331 }
332 /**
333 * Apply route group options like prefix and middleware to the route.
334 *
335 * This method is typically called when a route is defined within a group,
336 * applying any shared prefix or middleware from the group stack.
337 *
338 * @return void
339 *
340 * @since 1.0.0
341 */
342 public function apply_group_options()
343 {
344 if (!empty(static::$group_stack)) {
345 $group = array_last(static::$group_stack);
346 if (!empty($group['prefix'])) {
347 $this->endpoint = \rtrim($group['prefix'], '/') . '/' . \ltrim($this->endpoint, '/');
348 }
349 if (!empty($group['middleware'])) {
350 $this->middleware($group['middleware']);
351 }
352 }
353 }
354 /**
355 * Register the route with WordPress.
356 *
357 * @return void
358 *
359 * @since 1.0.0
360 */
361 public function register()
362 {
363 register_rest_route(static::$namespace, $this->get_formatted_endpoint(), ['methods' => \strtoupper($this->method), 'callback' => $this->resolve_route(), 'permission_callback' => fn($rest_request) => $this->resolve_permission_callback($rest_request)]);
364 }
365 /**
366 * Cache a class instance.
367 *
368 * @param string $abstract The class name to bind
369 * @param object $instance The instance of the class
370 *
371 * @return void
372 *
373 * @since 1.0.0
374 */
375 protected function cache(string $abstract, $instance)
376 {
377 static::$instances[$abstract] = $instance;
378 }
379 /**
380 * Check if a class instance is cached.
381 *
382 * @param string $abstract The class name to check
383 *
384 * @return bool
385 *
386 * @since 1.0.0
387 */
388 protected function is_cached(string $abstract)
389 {
390 return isset(static::$instances[$abstract]);
391 }
392 /**
393 * Get a cached class instance.
394 *
395 * @param string $abstract The class name to get
396 *
397 * @return object
398 *
399 * @since 1.0.0
400 */
401 protected function get_cached(string $abstract)
402 {
403 return static::$instances[$abstract];
404 }
405 /**
406 * Resolve a class and its dependencies.
407 *
408 * @param string $abstract The class name to resolve
409 * @param array $resolving Stack of classes being resolved (for
410 *
411 * @return object The resolved instance
412 *
413 * @throws \Exception
414 *
415 * @since 1.0.0
416 */
417 protected function make(string $abstract, array $resolving = [])
418 {
419 if ($this->is_cached($abstract)) {
420 return $this->get_cached($abstract);
421 }
422 if (\in_array($abstract, $resolving, \true)) {
423 throw new Exception(\sprintf('Circular dependency detected for class "%s".', $abstract));
424 }
425 if (!\class_exists($abstract)) {
426 throw new Exception(\sprintf('Class "%s" does not exist.', $abstract));
427 }
428 $reflector = new ReflectionClass($abstract);
429 if ($reflector->isAbstract()) {
430 throw new Exception(\sprintf('Class "%s" is abstract and cannot be instantiated.', $abstract));
431 }
432 $constructor = $reflector->getConstructor();
433 if (!$constructor) {
434 return new $abstract();
435 }
436 if (!$constructor->isPublic()) {
437 throw new Exception(\sprintf('Class "%s" has a non-public constructor and cannot be instantiated.', $abstract));
438 }
439 $dependencies = [];
440 $resolving[] = $abstract;
441 foreach ($constructor->getParameters() as $parameter) {
442 $type = $parameter->getType();
443 if (!$type) {
444 throw new Exception(\sprintf('Parameter "%s" is missing a type hint in the constructor. Please add a class type hint.', $parameter->getName()));
445 }
446 if ($type->isBuiltin()) {
447 throw new Exception(\sprintf(
448 'Parameter "%s" must be a class type, not a built-in type. Please specify a valid class dependency.',
449 // phpcs:ignore Generic.Files.LineLength.TooLong
450 $parameter->getName()
451 ));
452 }
453 $dependencies[] = $this->is_cached($type->getName()) ? $this->get_cached($type->getName()) : $this->make($type->getName(), $resolving);
454 }
455 $instance = $reflector->newInstanceArgs($dependencies);
456 $this->cache($abstract, $instance);
457 return $instance;
458 }
459 /**
460 * Make the method dependencies.
461 *
462 * @param string $abstract The class name to make the dependencies.
463 * @param string $method The method name to make the dependencies.
464 *
465 * @return array
466 *
467 * @throws \Exception
468 * @throws \InvalidArgumentException
469 *
470 * @since 1.0.0
471 */
472 protected function resolve_method_dependencies($abstract, $method)
473 {
474 $method_reflection = new ReflectionMethod($abstract, $method);
475 if (!$method_reflection->isPublic()) {
476 throw new Exception(\sprintf('Method "%s" is not public and cannot be called.', $method));
477 }
478 $parameters = $method_reflection->getParameters();
479 $dependencies = ['requests' => [], 'builtins' => [], 'models' => [], 'abstracts' => []];
480 foreach ($parameters as $parameter) {
481 $type = $parameter->getType() ?? 'string';
482 $variable = $parameter->getName();
483 $position = $parameter->getPosition();
484 $type_name = $type instanceof ReflectionNamedType ? $type->getName() : (string) $type;
485 if ($type === 'string' || $type->isBuiltin()) {
486 $dependencies['builtins'][] = $this->add_dependency($type_name, $variable, $position);
487 } elseif ($type_name === Request::class || $type_name === RequestContract::class || \is_subclass_of($type_name, Request::class)) {
488 // phpcs:ignore Generic.Files.LineLength.TooLong
489 $dependencies['requests'][] = $this->add_dependency($type_name, $variable, $position);
490 } elseif (\is_subclass_of($type_name, Model::class)) {
491 $dependencies['models'][] = $this->add_dependency($type_name, $variable, $position);
492 } else {
493 $dependencies['abstracts'][] = $this->add_dependency($type_name, $variable, $position);
494 }
495 }
496 if (\count($dependencies['requests']) < 1) {
497 throw new InvalidArgumentException(\sprintf('The method "%s" must have at least one request dependency.', $method));
498 }
499 if (\count($dependencies['requests']) > 1) {
500 throw new InvalidArgumentException(\sprintf('The method "%s" must have only one request dependency.', $method));
501 }
502 return $dependencies;
503 }
504 /**
505 * Add a dependency to the dependencies array.
506 *
507 * @param string $type The type of the dependency.
508 * @param string $variable The variable name of the dependency.
509 * @param int $position The position of the dependency.
510 *
511 * @return array
512 *
513 * @since 1.0.0
514 */
515 protected function add_dependency($type, $variable, $position)
516 {
517 return \compact('type', 'variable', 'position');
518 }
519 /**
520 * Add a resolved dependency to the dependencies array.
521 *
522 * @param mixed $resolved The resolved dependency.
523 * @param int $position The position of the dependency.
524 *
525 * @return array
526 *
527 * @since 1.0.0
528 */
529 protected function add_resolved_dependency($resolved, int $position)
530 {
531 return \compact('resolved', 'position');
532 }
533 /**
534 * Resolve the models.
535 *
536 * @param array $models The models to resolve.
537 * @param Request $request The request object.
538 *
539 * @return array
540 *
541 * @since 1.0.0
542 */
543 protected function resolve_models(array $models, Request $request)
544 {
545 $resolved_models = [];
546 foreach ($models as $model) {
547 $position = $model['position'];
548 $type = $model['type'];
549 $variable = $model['variable'];
550 $value = $request->get($variable);
551 $model = $this->resolve_model($type, $value);
552 $resolved_models[] = $this->add_resolved_dependency($model, $position);
553 }
554 return $resolved_models;
555 }
556 /**
557 * Resolve the built-in types.
558 *
559 * @param array $builtins The built-in types to resolve.
560 * @param Request $request The request object.
561 *
562 * @return array
563 *
564 * @since 1.0.0
565 */
566 protected function resolve_builtins(array $builtins, Request $request)
567 {
568 $resolved_builtins = [];
569 foreach ($builtins as $builtin) {
570 $type = $builtin['type'];
571 $variable = $builtin['variable'];
572 $position = $builtin['position'];
573 $value = $request->get($variable, null, $type);
574 $resolved_builtins[] = $this->add_resolved_dependency($value, $position);
575 }
576 return $resolved_builtins;
577 }
578 /**
579 * Resolve the abstracts.
580 *
581 * @param array $abstracts The abstracts to resolve.
582 * @param Request $request The request object.
583 *
584 * @return array
585 *
586 * @since 1.0.0
587 */
588 protected function resolve_abstracts(array $abstracts, Request $request)
589 {
590 $resolved_abstracts = [];
591 foreach ($abstracts as $abstract) {
592 $position = $abstract['position'];
593 $resolved = app()->make($abstract['type']);
594 $resolved_abstracts[] = $this->add_resolved_dependency($resolved, $position);
595 }
596 return $resolved_abstracts;
597 }
598 /**
599 * Resolve a model from the request.
600 *
601 * @param class-string<Model> $model The model class name
602 * @param mixed $value The value of the model
603 *
604 * @return Model
605 *
606 * @since 1.0.0
607 */
608 protected function resolve_model($model, $value)
609 {
610 $key_name = (new $model())->get_route_key();
611 try {
612 return $model::where($key_name, $value)->first_or_fail();
613 } catch (ModelNotFoundException $exception) {
614 $exception->set_model($model);
615 $exception->set_ids($value);
616 throw $exception;
617 }
618 }
619 /**
620 * Resolve the route handler.
621 *
622 * @return callable
623 *
624 * @throws InvalidRoutActionException
625 *
626 * @since 1.0.0
627 */
628 protected function resolve_route()
629 {
630 return $this->action instanceof Closure ? $this->resolve_closure_action() : $this->resolve_controller_action();
631 }
632 /**
633 * Resolve the closure route action.
634 *
635 * @return callable
636 *
637 * @since 1.0.0
638 */
639 protected function resolve_closure_action()
640 {
641 return function ($rest_request) {
642 try {
643 $request = $this->get_resolved_request($rest_request);
644 return ($this->action)($request);
645 } catch (Exception $exception) {
646 return ApiExceptionHandler::get_response($exception);
647 }
648 };
649 }
650 /**
651 * Resolve the controller route action.
652 *
653 * @return callable
654 *
655 * @since 1.0.0
656 */
657 protected function resolve_controller_action()
658 {
659 return function ($rest_request) {
660 try {
661 return $this->dispatch_controller($rest_request);
662 } catch (Exception $exception) {
663 return ApiExceptionHandler::get_response($exception);
664 }
665 };
666 }
667 /**
668 * Dispatch the controller action with the middleware-enriched request.
669 *
670 * @param WP_REST_Request $rest_request The REST request object.
671 *
672 * @return mixed
673 *
674 * @since 1.0.0
675 */
676 protected function dispatch_controller($rest_request)
677 {
678 $request = $this->get_resolved_request($rest_request);
679 $controller = $this->resolve_controller($request);
680 $dependecies = $this->update_request($controller['dependencies'], $this->add_resolved_dependency($request, $controller['request_position']));
681 $dependecies = $this->sort_dependencies($dependecies);
682 $parameters = (new Collection($dependecies))->pluck('resolved')->all();
683 $instance = $controller['instance'];
684 $method = $controller['method'];
685 return $instance->{$method}(...$parameters);
686 }
687 /**
688 * Resolve the controller for the route.
689 *
690 * @param Request $request The middleware-enriched request object.
691 *
692 * @return array
693 *
694 * @since 1.0.0
695 */
696 protected function resolve_controller(Request $request)
697 {
698 if (!\is_array($this->action)) {
699 throw new InvalidRoutActionException(\sprintf('Invalid method registered for the route %s', $this->endpoint));
700 }
701 if (\count($this->action) !== 2) {
702 throw new InvalidRoutActionException(\sprintf('Invalid controller syntax for the route %s', $this->endpoint));
703 }
704 [$controller, $method] = $this->action;
705 if (!\class_exists($controller)) {
706 throw new InvalidRoutActionException(\sprintf('Controller %s not found', $controller));
707 }
708 $controller_instance = $this->make($controller);
709 if (!\method_exists($controller_instance, $method)) {
710 throw new InvalidRoutActionException(\sprintf('The method %s is missing in the controller %s', $method, $controller));
711 }
712 $dependencies = $this->resolve_method_dependencies($controller_instance, $method);
713 $first_request = array_first($dependencies['requests']);
714 $request_position = $first_request['position'];
715 $dependency_array = $this->resolve_dependencies($dependencies, $request);
716 return ['instance' => $controller_instance, 'method' => $method, 'request' => $request, 'dependencies' => $dependency_array, 'request_position' => $request_position];
717 }
718 /**
719 * Build the middleware pipeline.
720 *
721 * @param callable $destination The destination callback.
722 *
723 * @return callable
724 *
725 * @since 1.0.0
726 */
727 protected function build_middleware_pipeline(callable $destination)
728 {
729 return \array_reduce(\array_reverse($this->middlewares), function ($next, $middleware) {
730 return function ($request) use($next, $middleware) {
731 if (!\is_subclass_of($middleware, Middleware::class)) {
732 throw new InvalidArgumentException(\sprintf('Middleware %s must implement the %s interface.', $middleware, Middleware::class));
733 }
734 return (new $middleware())->handle($request, $next);
735 };
736 }, $destination);
737 }
738 /**
739 * Resolve the permission callback for the route.
740 *
741 * @param WP_REST_Request $rest_request The REST request object.
742 *
743 * @return bool|WP_Error
744 *
745 * @since 1.0.0
746 */
747 protected function resolve_permission_callback($rest_request)
748 {
749 $request = $this->make_framework_request($rest_request);
750 try {
751 $request->authorize_request();
752 if (empty($this->middlewares)) {
753 $this->resolved_request = $this->expose($request);
754 return \true;
755 }
756 $pipeline = $this->build_middleware_pipeline(fn($request) => \true);
757 $pipeline($request);
758 $this->resolved_request = $this->expose($request);
759 return \true;
760 } catch (AuthorizationException $exception) {
761 return new WP_Error('rest_forbidden', $exception->getMessage(), ['status' => $exception->getCode()]);
762 }
763 }
764 /**
765 * Create a framework request from a WordPress REST request.
766 *
767 * @param WP_REST_Request $rest_request The REST request object.
768 *
769 * @return Request
770 *
771 * @since 1.0.0
772 */
773 protected function make_framework_request(WP_REST_Request $rest_request)
774 {
775 $request_class = $this->resolve_request_class();
776 return app()->make($request_class)->make_request($rest_request);
777 }
778 /**
779 * Resolve the request class from the route action.
780 *
781 * @return class-string<Request>
782 *
783 * @since 1.0.0
784 */
785 protected function resolve_request_class()
786 {
787 if ($this->action instanceof Closure) {
788 return $this->resolve_closure_request_class($this->action);
789 }
790 if (!\is_array($this->action) || \count($this->action) !== 2) {
791 return Request::class;
792 }
793 [$controller, $method] = $this->action;
794 if (!\class_exists($controller) || !\method_exists($controller, $method)) {
795 return Request::class;
796 }
797 $dependencies = $this->resolve_method_dependencies($controller, $method);
798 $first_request = array_first($dependencies['requests']);
799 return $this->normalize_request_class($first_request['type']);
800 }
801 /**
802 * Resolve the request class from a closure route action.
803 *
804 * @param Closure $closure The closure route action.
805 *
806 * @return class-string<Request>
807 *
808 * @since 1.0.0
809 */
810 protected function resolve_closure_request_class(Closure $closure)
811 {
812 $reflection = new ReflectionFunction($closure);
813 foreach ($reflection->getParameters() as $parameter) {
814 $type = $parameter->getType();
815 if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
816 continue;
817 }
818 $type_name = $type->getName();
819 if ($type_name === Request::class || $type_name === RequestContract::class || \is_subclass_of($type_name, Request::class)) {
820 return $this->normalize_request_class($type_name);
821 }
822 }
823 return Request::class;
824 }
825 /**
826 * Normalize a reflected request type to a concrete request class.
827 *
828 * @param string $type_name The reflected request type name.
829 *
830 * @return class-string<Request>
831 *
832 * @since 1.0.0
833 */
834 protected function normalize_request_class($type_name)
835 {
836 if ($type_name === RequestContract::class) {
837 return Request::class;
838 }
839 return $type_name;
840 }
841 /**
842 * Get the request enriched by middleware during permission checking.
843 *
844 * @param WP_REST_Request $rest_request The REST request object.
845 *
846 * @return Request
847 *
848 * @since 1.0.0
849 */
850 protected function get_resolved_request(WP_REST_Request $rest_request)
851 {
852 if (!\is_null($this->resolved_request)) {
853 return $this->resolved_request->validate_request();
854 }
855 $request = $this->expose($this->make_framework_request($rest_request));
856 return $request->validate_request();
857 }
858 /**
859 * Expose the request to the container to use
860 * the current request instance to the underneath classes and methods.
861 *
862 * @param Request $request The request object.
863 *
864 * @return Request
865 *
866 * @since 1.0.0
867 */
868 protected function expose(Request $request)
869 {
870 app()->instance('request', $request);
871 return $request;
872 }
873 /**
874 * Prepare the dependencies for the route. This will resolved the models,
875 * abstract classes like services, repositories, built-in types and requests.
876 * We are not appending the requests to the dependencies array because we will resolve them later
877 * after all the middlewares are handled.
878 *
879 * @param array $dependencies The dependencies of the route.
880 * @param Request $request The request object.
881 *
882 * @return array
883 *
884 * @since 1.0.0
885 */
886 protected function resolve_dependencies(array $dependencies, Request $request)
887 {
888 $models = $this->resolve_models($dependencies['models'], $request);
889 $builtins = $this->resolve_builtins($dependencies['builtins'], $request);
890 $abstracts = $this->resolve_abstracts($dependencies['abstracts'], $request);
891 return \array_values(\array_merge($models, $builtins, $abstracts));
892 }
893 /**
894 * Update the dependencies array with the resolved request.
895 * Here we are attaching the request with the dependencies.
896 * And this request is the request object after passing all the middlewares.
897 *
898 * @param array $dependencies The dependencies of the route.
899 * @param array $resolved_request The resolved request.
900 *
901 * @return array
902 *
903 * @since 1.0.0
904 */
905 protected function update_request(array $dependencies, array $resolved_request)
906 {
907 return \array_merge($dependencies, [$resolved_request]);
908 }
909 /**
910 * Sort the dependencies array by position so that it matches the original sequence of the dependencies.
911 *
912 * @param array $dependencies The dependencies of the route.
913 *
914 * @return array
915 *
916 * @since 1.0.0
917 */
918 protected function sort_dependencies(array $dependencies)
919 {
920 \usort($dependencies, function ($first, $second) {
921 return $first['position'] - $second['position'];
922 });
923 return $dependencies;
924 }
925 }
926