Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
132 / 132
93.91% covered (success)
93.91%
108 / 115
22.75% covered (danger)
22.75%
38 / 167
45.45% covered (danger)
45.45%
5 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
View
100.00% covered (success)
100.00%
132 / 132
93.91% covered (success)
93.91%
108 / 115
22.75% covered (danger)
22.75%
38 / 167
100.00% covered (success)
100.00%
11 / 11
1249.84
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
11 / 11
22.22% covered (danger)
22.22%
6 / 27
100.00% covered (success)
100.00%
1 / 1
16.76
 attributes
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 prepareView
100.00% covered (success)
100.00%
12 / 12
90.00% covered (success)
90.00%
9 / 10
66.67% covered (warning)
66.67%
4 / 6
100.00% covered (success)
100.00%
1 / 1
4.59
 prepareControllerAction
100.00% covered (success)
100.00%
10 / 10
93.75% covered (success)
93.75%
15 / 16
7.14% covered (danger)
7.14%
4 / 56
100.00% covered (success)
100.00%
1 / 1
46.23
 getClosure
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
8 / 8
80.00% covered (warning)
80.00%
4 / 5
100.00% covered (success)
100.00%
1 / 1
4.13
 getArgs
100.00% covered (success)
100.00%
19 / 19
91.67% covered (success)
91.67%
22 / 24
23.08% covered (danger)
23.08%
3 / 13
100.00% covered (success)
100.00%
1 / 1
45.87
 resolveUnknown
100.00% covered (success)
100.00%
6 / 6
90.00% covered (success)
90.00%
9 / 10
57.14% covered (warning)
57.14%
4 / 7
100.00% covered (success)
100.00%
1 / 1
5.26
 resolveParam
100.00% covered (success)
100.00%
21 / 21
95.00% covered (success)
95.00%
19 / 20
15.00% covered (danger)
15.00%
6 / 40
100.00% covered (success)
100.00%
1 / 1
58.74
 paramInfo
100.00% covered (success)
100.00%
14 / 14
88.89% covered (warning)
88.89%
8 / 9
25.00% covered (danger)
25.00%
2 / 8
100.00% covered (success)
100.00%
1 / 1
10.75
 middleware
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Router;
6
7use Celemas\Router\Exception\RuntimeException;
8use Celemas\Wire\Creator;
9use Celemas\Wire\Exception\WireException;
10use Closure;
11use Psr\Container\ContainerInterface as Container;
12use Psr\Http\Message\ResponseInterface as Response;
13use Psr\Http\Message\ServerRequestInterface as Request;
14use Psr\Http\Server\MiddlewareInterface as Middleware;
15use ReflectionClass;
16use ReflectionFunctionAbstract;
17use ReflectionMethod;
18use ReflectionNamedType;
19use ReflectionParameter;
20
21/** @internal */
22final class View
23{
24    use AddsBeforeAfter;
25
26    private Creator $creator;
27    private ?AttributesResolver $attributes = null;
28
29    /** @var Closure|array{class-string, string} */
30    private Closure|array $view;
31
32    /**
33     * @param list<Before> $beforeHandlers
34     * @param list<After> $afterHandlers
35     */
36    public function __construct(
37        protected readonly RouteMatch $match,
38        protected readonly ?Container $container,
39        array $beforeHandlers = [],
40        array $afterHandlers = [],
41    ) {
42        $route = $match->route();
43        $this->creator = new Creator($container);
44        $this->view = $this->prepareView($route->view());
45        $this->setBeforeHandlers($this->doMergeBeforeHandlers($beforeHandlers, $route->beforeHandlers()));
46        $this->setAfterHandlers($this->doMergeAfterHandlers($afterHandlers, $route->afterHandlers()));
47    }
48
49    public function execute(Request $request): Response
50    {
51        $closure = $this->getClosure($request);
52
53        /** @var list<Before> $beforeAttributes */
54        $beforeAttributes = $this->attributes(Before::class);
55        foreach ($this->mergeBeforeHandlers($beforeAttributes) as $handler) {
56            $request = $handler->handle($request);
57        }
58
59        /** @psalm-suppress MixedAssignment -- views may return arbitrary data for after handlers */
60        $result = $closure(...$this->getArgs(getReflectionFunction($closure), $request));
61
62        /** @var list<After> $afterAttributes */
63        $afterAttributes = $this->attributes(After::class);
64        foreach ($this->mergeAfterHandlers($afterAttributes) as $handler) {
65            /** @psalm-suppress MixedAssignment -- after handlers may transform arbitrary data */
66            $result = $handler->handle($result);
67        }
68
69        if ($result instanceof Response) {
70            return $result;
71        }
72
73        if ($result instanceof ResponseWrapper) {
74            return $result->unwrap();
75        }
76
77        throw new RuntimeException(
78            'Unable to determine a response handler for the returned value of the view',
79        );
80    }
81
82    /** @param ?class-string $filter */
83    public function attributes(?string $filter = null): array
84    {
85        if (!isset($this->attributes)) {
86            if ($this->view instanceof Closure) {
87                $this->attributes = new AttributesResolver(
88                    [getReflectionFunction($this->view)],
89                    $this->container,
90                );
91            } else {
92                [$controller, $method] = $this->view;
93                $reflectionClass = new ReflectionClass($controller);
94                $this->attributes = new AttributesResolver([
95                    $reflectionClass,
96                    $reflectionClass->getMethod($method),
97                ], $this->container);
98            }
99        }
100
101        return $this->attributes->get($filter);
102    }
103
104    /** @return Closure|array{class-string, string} */
105    private function prepareView(callable|string|array $view): Closure|array
106    {
107        if (is_callable($view)) {
108            return Closure::fromCallable($view);
109        }
110
111        if (is_array($view)) {
112            return $this->prepareControllerAction($view[0] ?? null, $view[1] ?? null);
113        }
114
115        if (class_exists($view)) {
116            return $this->prepareControllerAction($view, '__invoke');
117        }
118
119        throw new RuntimeException(
120            'Route action string is not callable: '
121            . $view
122            . ". Use a callable, [Controller::class, 'method'], an invokable controller class, "
123            . 'or a controller group.',
124        );
125    }
126
127    /** @return array{class-string, string} */
128    private function prepareControllerAction(mixed $controllerName, mixed $method): array
129    {
130        if (
131            !is_string($controllerName)
132            || $controllerName === ''
133            || !is_string($method)
134            || $method === ''
135        ) {
136            throw new RuntimeException("Controller actions must use [Controller::class, 'method'].");
137        }
138
139        if (!class_exists($controllerName)) {
140            throw new RuntimeException('Route controller not found: ' . $controllerName);
141        }
142
143        if (!method_exists($controllerName, $method)) {
144            throw new RuntimeException('Route action method not found: ' . $controllerName . '::' . $method);
145        }
146
147        return [$controllerName, $method];
148    }
149
150    private function getClosure(Request $request): Closure
151    {
152        if ($this->view instanceof Closure) {
153            return $this->view;
154        }
155
156        [$controllerName, $method] = $this->view;
157        $rc = new ReflectionClass($controllerName);
158        $constructor = $rc->getConstructor();
159        $args = $constructor ? $this->getArgs($constructor, $request) : [];
160        $controller = $rc->newInstance(...$args);
161
162        if (is_callable([$controller, $method])) {
163            return Closure::fromCallable([$controller, $method]);
164        }
165
166        throw new RuntimeException(
167            'Route action method is not callable: ' . $controllerName . '::' . $method,
168        );
169    }
170
171    /**
172     * Determines the arguments passed to the view and/or controller constructor.
173     *
174     * - If a view parameter implements Request, the request will be passed.
175     * - If a view parameter is a subclass of Route, the route will be passed.
176     * - If names of the view parameters match names of the route arguments
177     *   it will try to convert the argument to the parameter type and add it to
178     *   the returned args list.
179     * - If the parameter is typed, try to resolve it via container or
180     *   autowiring.
181     * - If parameter resolution fails and a default value exists, the default
182     *   will be used.
183     * - Exceptions thrown while constructing dependencies bubble unchanged.
184     * - Otherwise fail.
185     *
186     * @psalm-suppress MixedAssignment -- $args values are mixed
187     */
188    private function getArgs(ReflectionFunctionAbstract $rf, Request $request): array
189    {
190        /** @var array<string, mixed> $args */
191        $args = [];
192        $params = $rf->getParameters();
193        $errMsg = 'View parameters cannot be resolved. Details: ';
194
195        foreach ($params as $param) {
196            $name = $param->getName();
197            $routeArgs = $this->match->params();
198
199            if (array_key_exists($name, $routeArgs)) {
200                $args[$name] = match ((string) $param->getType()) {
201                    'int' => is_numeric($routeArgs[$name])
202                        ? (int) $routeArgs[$name]
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
204                    'float' => is_numeric($routeArgs[$name])
205                        ? (float) $routeArgs[$name]
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
207                    'string' => $routeArgs[$name],
208                    default => $this->resolveUnknown($param, $request, $errMsg),
209                };
210            } else {
211                $args[$name] = $this->resolveUnknown($param, $request, $errMsg);
212            }
213        }
214
215        assert(count($params) === count($args), 'Resolved argument count must match parameter count.');
216
217        return $args;
218    }
219
220    private function resolveUnknown(
221        ReflectionParameter $param,
222        Request $request,
223        string $errMsg,
224    ): mixed {
225        try {
226            return $this->resolveParam($param, $request);
227        } catch (RuntimeException|WireException $e) {
228            if ($param->isDefaultValueAvailable()) {
229                return $param->getDefaultValue();
230            }
231
232            $code = $e->getCode();
233
234            throw new RuntimeException($errMsg . $e->getMessage(), is_int($code) ? $code : 0, $e);
235        }
236    }
237
238    private function resolveParam(ReflectionParameter $param, Request $request): mixed
239    {
240        $type = $param->getType();
241
242        if ($type instanceof ReflectionNamedType) {
243            $typeName = ltrim($type->getName(), '?');
244
245            if ($typeName === Request::class || is_subclass_of($typeName, Request::class)) {
246                return $request;
247            }
248
249            if ($typeName === Route::class || is_subclass_of($typeName, Route::class)) {
250                return $this->match->route();
251            }
252
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
254                throw new RuntimeException(
255                    "Type '{$typeName}' is not a class or interface. Source: \n" . $this->paramInfo($param),
256                );
257            }
258
259            return $this->creator->create($typeName, predefinedTypes: [Request::class => $request]);
260        }
261        if ($type) {
262            throw new RuntimeException(
263                "Autowiring does not support union or intersection types. Source: \n"
264                    . $this->paramInfo($param),
265            );
266        }
267
268        throw new RuntimeException(
269            "Autowired entities need to have typed constructor parameters. Source: \n"
270                . $this->paramInfo($param),
271        );
272    }
273
274    public function paramInfo(ReflectionParameter $param): string
275    {
276        $type = $param->getType();
277        $rf = $param->getDeclaringFunction();
278        $rc = null;
279
280        if ($rf instanceof ReflectionMethod) {
281            $rc = $rf->getDeclaringClass();
282        }
283
284        return (
285            ($rc ? $rc->getName() . '::' : '')
286            . $rf->getName()
287            . '(..., '
288            . ($type ? (string) $type . ' ' : '')
289            . '$'
290            . $param->getName()
291            . ', ...)'
292        );
293    }
294
295    /** @return list<Middleware> */
296    public function middleware(): array
297    {
298        /** @var list<Middleware> $middlewareAttributes */
299        $middlewareAttributes = $this->attributes(Middleware::class);
300
301        return array_merge(
302            $this->match->route()->getMiddleware(),
303            $middlewareAttributes,
304        );
305    }
306}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

View->__construct
37        protected readonly RouteMatch $match,
38        protected readonly ?Container $container,
39        array $beforeHandlers = [],
40        array $afterHandlers = [],
41    ) {
42        $route = $match->route();
43        $this->creator = new Creator($container);
44        $this->view = $this->prepareView($route->view());
45        $this->setBeforeHandlers($this->doMergeBeforeHandlers($beforeHandlers, $route->beforeHandlers()));
46        $this->setAfterHandlers($this->doMergeAfterHandlers($afterHandlers, $route->afterHandlers()));
47    }
View->attributes
83    public function attributes(?string $filter = null): array
84    {
85        if (!isset($this->attributes)) {
86            if ($this->view instanceof Closure) {
86            if ($this->view instanceof Closure) {
87                $this->attributes = new AttributesResolver(
92                [$controller, $method] = $this->view;
93                $reflectionClass = new ReflectionClass($controller);
94                $this->attributes = new AttributesResolver([
95                    $reflectionClass,
96                    $reflectionClass->getMethod($method),
97                ], $this->container);
98            }
99        }
100
101        return $this->attributes->get($filter);
101        return $this->attributes->get($filter);
102    }
View->execute
49    public function execute(Request $request): Response
50    {
51        $closure = $this->getClosure($request);
52
53        /** @var list<Before> $beforeAttributes */
54        $beforeAttributes = $this->attributes(Before::class);
55        foreach ($this->mergeBeforeHandlers($beforeAttributes) as $handler) {
55        foreach ($this->mergeBeforeHandlers($beforeAttributes) as $handler) {
55        foreach ($this->mergeBeforeHandlers($beforeAttributes) as $handler) {
56            $request = $handler->handle($request);
55        foreach ($this->mergeBeforeHandlers($beforeAttributes) as $handler) {
56            $request = $handler->handle($request);
57        }
58
59        /** @psalm-suppress MixedAssignment -- views may return arbitrary data for after handlers */
60        $result = $closure(...$this->getArgs(getReflectionFunction($closure), $request));
61
62        /** @var list<After> $afterAttributes */
63        $afterAttributes = $this->attributes(After::class);
64        foreach ($this->mergeAfterHandlers($afterAttributes) as $handler) {
64        foreach ($this->mergeAfterHandlers($afterAttributes) as $handler) {
64        foreach ($this->mergeAfterHandlers($afterAttributes) as $handler) {
65            /** @psalm-suppress MixedAssignment -- after handlers may transform arbitrary data */
66            $result = $handler->handle($result);
64        foreach ($this->mergeAfterHandlers($afterAttributes) as $handler) {
65            /** @psalm-suppress MixedAssignment -- after handlers may transform arbitrary data */
66            $result = $handler->handle($result);
67        }
68
69        if ($result instanceof Response) {
70            return $result;
73        if ($result instanceof ResponseWrapper) {
74            return $result->unwrap();
77        throw new RuntimeException(
78            'Unable to determine a response handler for the returned value of the view',
79        );
80    }
View->getArgs
188    private function getArgs(ReflectionFunctionAbstract $rf, Request $request): array
189    {
190        /** @var array<string, mixed> $args */
191        $args = [];
192        $params = $rf->getParameters();
193        $errMsg = 'View parameters cannot be resolved. Details: ';
194
195        foreach ($params as $param) {
195        foreach ($params as $param) {
196            $name = $param->getName();
197            $routeArgs = $this->match->params();
198
199            if (array_key_exists($name, $routeArgs)) {
200                $args[$name] = match ((string) $param->getType()) {
201                    'int' => is_numeric($routeArgs[$name])
201                    'int' => is_numeric($routeArgs[$name])
201                    'int' => is_numeric($routeArgs[$name])
201                    'int' => is_numeric($routeArgs[$name])
202                        ? (int) $routeArgs[$name]
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
204                    'float' => is_numeric($routeArgs[$name])
204                    'float' => is_numeric($routeArgs[$name])
204                    'float' => is_numeric($routeArgs[$name])
204                    'float' => is_numeric($routeArgs[$name])
205                        ? (float) $routeArgs[$name]
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
207                    'string' => $routeArgs[$name],
208                    default => $this->resolveUnknown($param, $request, $errMsg),
199            if (array_key_exists($name, $routeArgs)) {
200                $args[$name] = match ((string) $param->getType()) {
201                    'int' => is_numeric($routeArgs[$name])
202                        ? (int) $routeArgs[$name]
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
204                    'float' => is_numeric($routeArgs[$name])
205                        ? (float) $routeArgs[$name]
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
207                    'string' => $routeArgs[$name],
208                    default => $this->resolveUnknown($param, $request, $errMsg),
195        foreach ($params as $param) {
196            $name = $param->getName();
197            $routeArgs = $this->match->params();
198
199            if (array_key_exists($name, $routeArgs)) {
200                $args[$name] = match ((string) $param->getType()) {
201                    'int' => is_numeric($routeArgs[$name])
202                        ? (int) $routeArgs[$name]
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
204                    'float' => is_numeric($routeArgs[$name])
205                        ? (float) $routeArgs[$name]
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
207                    'string' => $routeArgs[$name],
208                    default => $this->resolveUnknown($param, $request, $errMsg),
209                };
210            } else {
211                $args[$name] = $this->resolveUnknown($param, $request, $errMsg);
195        foreach ($params as $param) {
195        foreach ($params as $param) {
196            $name = $param->getName();
197            $routeArgs = $this->match->params();
198
199            if (array_key_exists($name, $routeArgs)) {
200                $args[$name] = match ((string) $param->getType()) {
201                    'int' => is_numeric($routeArgs[$name])
202                        ? (int) $routeArgs[$name]
203                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to int"),
204                    'float' => is_numeric($routeArgs[$name])
205                        ? (float) $routeArgs[$name]
206                        : throw new RuntimeException($errMsg . "Cannot cast '{$name}' to float"),
207                    'string' => $routeArgs[$name],
208                    default => $this->resolveUnknown($param, $request, $errMsg),
209                };
210            } else {
211                $args[$name] = $this->resolveUnknown($param, $request, $errMsg);
212            }
213        }
214
215        assert(count($params) === count($args), 'Resolved argument count must match parameter count.');
216
217        return $args;
218    }
View->getClosure
150    private function getClosure(Request $request): Closure
151    {
152        if ($this->view instanceof Closure) {
153            return $this->view;
156        [$controllerName, $method] = $this->view;
157        $rc = new ReflectionClass($controllerName);
158        $constructor = $rc->getConstructor();
159        $args = $constructor ? $this->getArgs($constructor, $request) : [];
159        $args = $constructor ? $this->getArgs($constructor, $request) : [];
159        $args = $constructor ? $this->getArgs($constructor, $request) : [];
159        $args = $constructor ? $this->getArgs($constructor, $request) : [];
160        $controller = $rc->newInstance(...$args);
161
162        if (is_callable([$controller, $method])) {
163            return Closure::fromCallable([$controller, $method]);
166        throw new RuntimeException(
167            'Route action method is not callable: ' . $controllerName . '::' . $method,
168        );
169    }
View->middleware
299        $middlewareAttributes = $this->attributes(Middleware::class);
300
301        return array_merge(
302            $this->match->route()->getMiddleware(),
303            $middlewareAttributes,
304        );
305    }
View->paramInfo
274    public function paramInfo(ReflectionParameter $param): string
275    {
276        $type = $param->getType();
277        $rf = $param->getDeclaringFunction();
278        $rc = null;
279
280        if ($rf instanceof ReflectionMethod) {
281            $rc = $rf->getDeclaringClass();
282        }
283
284        return (
285            ($rc ? $rc->getName() . '::' : '')
285            ($rc ? $rc->getName() . '::' : '')
285            ($rc ? $rc->getName() . '::' : '')
285            ($rc ? $rc->getName() . '::' : '')
286            . $rf->getName()
286            . $rf->getName()
287            . '(..., '
288            . ($type ? (string) $type . ' ' : '')
288            . ($type ? (string) $type . ' ' : '')
288            . ($type ? (string) $type . ' ' : '')
288            . ($type ? (string) $type . ' ' : '')
289            . '$'
290            . $param->getName()
291            . ', ...)'
292        );
293    }
View->prepareControllerAction
128    private function prepareControllerAction(mixed $controllerName, mixed $method): array
129    {
130        if (
131            !is_string($controllerName)
132            || $controllerName === ''
132            || $controllerName === ''
133            || !is_string($method)
133            || !is_string($method)
134            || $method === ''
134            || $method === ''
136            throw new RuntimeException("Controller actions must use [Controller::class, 'method'].");
139        if (!class_exists($controllerName)) {
139        if (!class_exists($controllerName)) {
139        if (!class_exists($controllerName)) {
139        if (!class_exists($controllerName)) {
140            throw new RuntimeException('Route controller not found: ' . $controllerName);
143        if (!method_exists($controllerName, $method)) {
144            throw new RuntimeException('Route action method not found: ' . $controllerName . '::' . $method);
147        return [$controllerName, $method];
148    }
View->prepareView
105    private function prepareView(callable|string|array $view): Closure|array
106    {
107        if (is_callable($view)) {
108            return Closure::fromCallable($view);
111        if (is_array($view)) {
112            return $this->prepareControllerAction($view[0] ?? null, $view[1] ?? null);
115        if (class_exists($view)) {
115        if (class_exists($view)) {
115        if (class_exists($view)) {
115        if (class_exists($view)) {
116            return $this->prepareControllerAction($view, '__invoke');
119        throw new RuntimeException(
120            'Route action string is not callable: '
121            . $view
122            . ". Use a callable, [Controller::class, 'method'], an invokable controller class, "
123            . 'or a controller group.',
124        );
125    }
View->resolveParam
238    private function resolveParam(ReflectionParameter $param, Request $request): mixed
239    {
240        $type = $param->getType();
241
242        if ($type instanceof ReflectionNamedType) {
243            $typeName = ltrim($type->getName(), '?');
244
245            if ($typeName === Request::class || is_subclass_of($typeName, Request::class)) {
245            if ($typeName === Request::class || is_subclass_of($typeName, Request::class)) {
245            if ($typeName === Request::class || is_subclass_of($typeName, Request::class)) {
246                return $request;
249            if ($typeName === Route::class || is_subclass_of($typeName, Route::class)) {
249            if ($typeName === Route::class || is_subclass_of($typeName, Route::class)) {
249            if ($typeName === Route::class || is_subclass_of($typeName, Route::class)) {
250                return $this->match->route();
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
253            if (!class_exists($typeName) && !interface_exists($typeName)) {
254                throw new RuntimeException(
255                    "Type '{$typeName}' is not a class or interface. Source: \n" . $this->paramInfo($param),
259            return $this->creator->create($typeName, predefinedTypes: [Request::class => $request]);
261        if ($type) {
262            throw new RuntimeException(
263                "Autowiring does not support union or intersection types. Source: \n"
264                    . $this->paramInfo($param),
268        throw new RuntimeException(
269            "Autowired entities need to have typed constructor parameters. Source: \n"
270                . $this->paramInfo($param),
271        );
272    }
View->resolveUnknown
221        ReflectionParameter $param,
222        Request $request,
223        string $errMsg,
224    ): mixed {
225        try {
226            return $this->resolveParam($param, $request);
227        } catch (RuntimeException|WireException $e) {
227        } catch (RuntimeException|WireException $e) {
227        } catch (RuntimeException|WireException $e) {
228            if ($param->isDefaultValueAvailable()) {
229                return $param->getDefaultValue();
232            $code = $e->getCode();
233
234            throw new RuntimeException($errMsg . $e->getMessage(), is_int($code) ? $code : 0, $e);
234            throw new RuntimeException($errMsg . $e->getMessage(), is_int($code) ? $code : 0, $e);
234            throw new RuntimeException($errMsg . $e->getMessage(), is_int($code) ? $code : 0, $e);
234            throw new RuntimeException($errMsg . $e->getMessage(), is_int($code) ? $code : 0, $e);
235        }
236    }