Passed
Push — master ( 9b8457...f05bc0 )
by Alexander
01:49
created

Request::setRouteFromRouter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l;
4
5
use alkemann\h2l\exceptions\InvalidUrl;
6
use alkemann\h2l\exceptions\NoRouteSetError;
7
8
/**
9
 * Class Request
10
 *
11
 * @package alkemann\h2l
12
 */
13
class Request
14
{
15
    const GET = 'GET';
16
    const PATCH = 'PATCH';
17
    const POST = 'POST';
18
    const PUT = 'PUT';
19
    const DELETE = 'DELETE';
20
21
    private $middlewares = [];
22
    private $request;
23
    private $server;
24
    private $parameters;
25
    private $headers;
26
    private $get;
27
    private $post;
28
    private $url;
29
    private $method;
30
    private $type = 'html';
31
32
    /**
33
     * @var interfaces\Session
34
     */
35
    private $session;
36
37
    /**
38
     * @var interfaces\Route
39
     */
40
    protected $route;
41
42
    /**
43
     * Analyze request, provided $_REQUEST, $_SERVER [, $_GET, $_POST] to identify Route
44
     *
45
     * Response type can be set from HTTP_ACCEPT header
46
     * Call setRoute or setRouteFromRouter to set a route
47
     *
48
     * @param array $request $_REQUEST
49
     * @param array $server $_SERVER
50
     * @param array $get $_GET
51
     * @param array $post $_POST
52
     * @param null|interfaces\Session $session if null, a default Session with $_SESSION will be created
53
     */
54
    public function __construct(
55
        array $request = [],
56
        array $server = [],
57
        array $get = [],
58
        array $post = [],
59
        interfaces\Session $session = null
60
    ) {
61
        $this->request = $request;
62
        $this->server = $server;
63
        $this->post = $post;
64
        $this->get = $get;
65
        unset($this->get['url']); // @TODO Use a less important keyword, as it blocks that _GET param?
66
        $this->parameters = [];
67
        $this->headers = Util::getRequestHeadersFromServerArray($server);
68
69
        // override html type with json
70
        $http_accept = $this->server['HTTP_ACCEPT'] ?? '*/*';
71
        if ($http_accept !== '*/*' && strpos($http_accept, 'application/json') !== false) {
72
            $this->type = 'json';
73
        }
74
75
        if (is_null($session)) {
76
            $this->session = new Session;
77
        } else {
78
            $this->session = $session;
79
        }
80
81
        $this->url = $this->request['url'] ?? '/';
82
        $this->method = $this->server['REQUEST_METHOD'] ?? Request::GET;
83
    }
84
85
    public function setRouteFromRouter(string $router = Router::class): bool
86
    {
87
        $this->route = $router::match($this->url, $this->method);
88
        if (is_null($this->route)) {
89
            return false;
90
        }
91
        $this->parameters = $this->route->parameters();
92
        return true;
93
    }
94
95
    public function getHeader(string $name): ?string
96
    {
97
        return $this->headers[$name] ?? null;
98
    }
99
100
    public function getHeaders(): array
101
    {
102
        return $this->headers;
103
    }
104
105
    /**
106
     * @TODO inspect request headers for content type, auto parse the body
107
     * @codeCoverageIgnore
108
     * @return string the raw 'php://input' post
109
     */
110
    public function getPostBody(): string
111
    {
112
        return file_get_contents('php://input');
113
    }
114
115
    /**
116
     * @return interfaces\Route|null Route identified for request if set
117
     */
118
    public function route(): ?interfaces\Route
119
    {
120
        return $this->route;
121
    }
122
123
    /**
124
     * @param interfaces\Route $route
125
     */
126
    public function setRoute(interfaces\Route $route): void
127
    {
128
        $this->route = $route;
129
        $this->parameters = $route->parameters();
130
    }
131
132
    /**
133
     * @return string the requested url
134
     */
135
    public function url(): string
136
    {
137
        return $this->url;
138
    }
139
140
    /**
141
     * @return string Request::GET, Request::POST, Request::PATCH, Request::PATCH etc
142
     */
143
    public function method(): string
144
    {
145
        return $this->method;
146
    }
147
148
    /**
149
     * @return string 'html', 'json', 'xml' etc
150
     */
151
    public function type(): string
152
    {
153
        return $this->type;
154
    }
155
156
    /**
157
     * Get request parameters from url as url parats, get queries or post, in that order
158
     *
159
     * @param string $name the name of the parameter
160
     * @return mixed|null the value or null if not set
161
     */
162
    public function param(string $name)
163
    {
164
        if (isset($this->parameters[$name])) {
165
            return $this->parameters[$name];
166
        }
167
        if (isset($this->get[$name])) {
168
            return $this->get[$name];
169
        }
170
        if (isset($this->post[$name])) {
171
            return $this->post[$name];
172
        }
173
        return null;
174
    }
175
176
    /**
177
     * @return array $_GET
178
     */
179
    public function query(): array
180
    {
181
        return $this->get;
182
    }
183
184
    /**
185
     * Execute the Route's callback and return the Response object that the callback is required to return
186
     *
187
     * Catches InvalidUrl exceptions and returns a response\Error with 404 instead
188
     *
189
     * @return Response|null
190
     * @throws NoRouteSetError if a route has not been set prior to calling this, or by a middleware
191
     */
192
    public function response(): ?Response
193
    {
194
        $cbs = $this->middlewares;
195
        $call_eventual_route_at_end_of_chain = function(Request $request, Chain $chain): ?Response {
0 ignored issues
show
Unused Code introduced by
The parameter $chain is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

195
        $call_eventual_route_at_end_of_chain = function(Request $request, /** @scrutinizer ignore-unused */ Chain $chain): ?Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
196
            $route = $request->route();
197
            if (is_null($route)) {
198
                throw new NoRouteSetError("Response called without Route set");
199
            }
200
            try {
201
                return $route($request);
202
            } catch (InvalidUrl $e) {
203
                return new response\Error(['message' => $e->getMessage()], ['code' => 404]);
204
            }
205
        };
206
        array_push($cbs, $call_eventual_route_at_end_of_chain);
207
        $response = (new Chain($cbs))->next($this);
208
        return ($response instanceof Response) ? $response : null;
209
    }
210
211
    /**
212
     * Add a closure to wrap the Route callback in to be called during Request::response
213
     *
214
     * @param callable|callable[] ...$cbs
215
     */
216
    public function registerMiddle(callable ...$cbs): void
217
    {
218
        foreach ($cbs as $cb) {
219
            $this->middlewares[] = $cb;
220
        }
221
    }
222
223
    /**
224
     * Returns the session var at $key or the Session object
225
     *
226
     * First call to this method will initiate the session
227
     *
228
     * @param string $key Dot notation for deeper values, i.e. `user.email`
229
     * @return mixed|interfaces\Session
230
     */
231
    public function session(?string $key = null)
232
    {
233
        if (is_null($key)) {
234
            return $this->session;
235
        }
236
        return $this->session->get($key);
237
    }
238
239
    /**
240
     * Redirect NOW the request to $url
241
     *
242
     * @codeCoverageIgnore
243
     * @param $url
244
     */
245
    public function redirect($url)
246
    {
247
        // @TODO add support for reverse route match
248
        header("Location: " . $url);
249
        exit;
250
    }
251
}
252