Passed
Push — master ( 11dd35...5da5e2 )
by Melech
03:58
created

Router::getResponseForDispatch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Routing;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use Valkyrja\Container\Contract\Container;
18
use Valkyrja\Dispatcher\Contract\Dispatcher;
19
use Valkyrja\Http\Message\Enum\StatusCode;
20
use Valkyrja\Http\Message\Exception\HttpException;
21
use Valkyrja\Http\Message\Factory\Contract\ResponseFactory;
22
use Valkyrja\Http\Message\Factory\ResponseFactory as HttpMessageResponseFactory;
23
use Valkyrja\Http\Message\Request\Contract\ServerRequest;
24
use Valkyrja\Http\Message\Response\Contract\Response;
25
use Valkyrja\Http\Middleware;
26
use Valkyrja\Http\Middleware\Handler\Contract\RouteDispatchedHandler;
27
use Valkyrja\Http\Middleware\Handler\Contract\RouteMatchedHandler;
28
use Valkyrja\Http\Middleware\Handler\Contract\RouteNotMatchedHandler;
29
use Valkyrja\Http\Middleware\Handler\Contract\SendingResponseHandler;
30
use Valkyrja\Http\Middleware\Handler\Contract\TerminatedHandler;
31
use Valkyrja\Http\Middleware\Handler\Contract\ThrowableCaughtHandler;
32
use Valkyrja\Http\Routing\Contract\Router as Contract;
33
use Valkyrja\Http\Routing\Data\Contract\Route;
34
use Valkyrja\Http\Routing\Exception\InvalidRouteNameException;
35
use Valkyrja\Http\Routing\Matcher\Contract\Matcher;
36
37
use function rawurldecode;
38
39
/**
40
 * Class Router.
41
 *
42
 * @author Melech Mizrachi
43
 */
44
class Router implements Contract
45
{
46
    /**
47
     * Router constructor.
48
     */
49
    public function __construct(
50
        protected Container $container = new \Valkyrja\Container\Container(),
51
        protected Dispatcher $dispatcher = new \Valkyrja\Dispatcher\Dispatcher(),
52
        protected Matcher $matcher = new \Valkyrja\Http\Routing\Matcher\Matcher(),
53
        protected ResponseFactory $responseFactory = new HttpMessageResponseFactory(),
54
        protected ThrowableCaughtHandler $throwableCaughtHandler = new Middleware\Handler\ThrowableCaughtHandler(),
55
        protected RouteMatchedHandler $routeMatchedHandler = new Middleware\Handler\RouteMatchedHandler(),
56
        protected RouteNotMatchedHandler $routeNotMatchedHandler = new Middleware\Handler\RouteNotMatchedHandler(),
57
        protected RouteDispatchedHandler $routeDispatchedHandler = new Middleware\Handler\RouteDispatchedHandler(),
58
        protected SendingResponseHandler $sendingResponseHandler = new Middleware\Handler\SendingResponseHandler(),
59
        protected TerminatedHandler $terminatedHandler = new Middleware\Handler\TerminatedHandler()
60
    ) {
61
    }
62
63
    /**
64
     * @inheritDoc
65
     */
66
    #[Override]
67
    public function dispatch(ServerRequest $request): Response
68
    {
69
        // Attempt to match the route
70
        $matchedRoute = $this->attemptToMatchRoute($request);
71
72
        // If the route was not matched a response returned
73
        if ($matchedRoute instanceof Response) {
74
            // Dispatch RouteNotMatchedMiddleware
75
            return $this->routeNotMatchedHandler->routeNotMatched(
76
                request: $request,
77
                response: $matchedRoute
78
            );
79
        }
80
81
        return $this->dispatchRoute(
82
            request: $request,
83
            route: $matchedRoute
84
        );
85
    }
86
87
    /**
88
     * @inheritDoc
89
     */
90
    #[Override]
91
    public function dispatchRoute(ServerRequest $request, Route $route): Response
92
    {
93
        // The route has been matched
94
        $this->routeMatched($route);
95
96
        // Dispatch the RouteMatchedMiddleware
97
        $routeAfterMiddleware = $this->routeMatchedHandler->routeMatched(
98
            request: $request,
99
            route: $route
100
        );
101
102
        // If the return value after middleware is a response return it
103
        if ($routeAfterMiddleware instanceof Response) {
104
            return $routeAfterMiddleware;
105
        }
106
107
        // Set the route after middleware has potentially modified it in the service container
108
        $this->container->setSingleton(Route::class, $routeAfterMiddleware);
109
110
        $dispatch  = $routeAfterMiddleware->getDispatch();
111
        $arguments = $dispatch->getArguments();
112
113
        // Attempt to dispatch the route using any one of the callable options
114
        $response = $this->dispatcher->dispatch(
115
            dispatch: $dispatch,
116
            arguments: $arguments
117
        );
118
119
        if (! $response instanceof Response) {
120
            throw new InvalidRouteNameException('Dispatch must be a valid response');
121
        }
122
123
        return $this->routeDispatchedHandler->routeDispatched(
124
            request: $request,
125
            response: $response,
126
            route: $routeAfterMiddleware
127
        );
128
    }
129
130
    /**
131
     * Match a route, or a response if no route exists, from a given server request.
132
     *
133
     * @param ServerRequest $request The request
134
     *
135
     * @throws HttpException
136
     *
137
     * @return Route|Response
138
     */
139
    protected function attemptToMatchRoute(ServerRequest $request): Route|Response
140
    {
141
        // Decode the request uri
142
        /** @var non-empty-string $requestPath */
143
        $requestPath = rawurldecode($request->getUri()->getPath());
144
        // Try to match the route
145
        $route = $this->matcher->match(
146
            path: $requestPath,
147
            requestMethod: $request->getMethod()
148
        );
149
150
        // Return the route if it was found
151
        if ($route !== null) {
152
            return $route;
153
        }
154
155
        // If the route matches for any method
156
        if ($this->matcher->match($requestPath) !== null) {
157
            // Then the route exists but not for the requested method, and so it is not allowed
158
            return $this->responseFactory->createResponse(
159
                statusCode: StatusCode::METHOD_NOT_ALLOWED,
160
            );
161
        }
162
163
        // Otherwise return a response with a 404
164
        return $this->responseFactory->createResponse(
165
            statusCode: StatusCode::NOT_FOUND,
166
        );
167
    }
168
169
    /**
170
     * Do various stuff after the route has been matched.
171
     *
172
     * @param Route $route The route
173
     *
174
     * @return void
175
     */
176
    protected function routeMatched(Route $route): void
177
    {
178
        $this->routeMatchedHandler->add(...$route->getRouteMatchedMiddleware());
179
        $this->routeDispatchedHandler->add(...$route->getRouteDispatchedMiddleware());
180
        $this->throwableCaughtHandler->add(...$route->getThrowableCaughtMiddleware());
181
        $this->sendingResponseHandler->add(...$route->getSendingResponseMiddleware());
182
        $this->terminatedHandler->add(...$route->getTerminatedMiddleware());
183
184
        // Set the found route in the service container
185
        $this->container->setSingleton(Route::class, $route);
186
    }
187
}
188