Completed
Push — master ( 064b01...9f2ca1 )
by Siro Díaz
03:18
created

Router::getRequestHeaders()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 14
rs 8.8571
cc 6
eloc 8
nc 4
nop 0
1
<?php
2
3
namespace Routify;
4
5
6
class Router {
7
8
    /**
9
     * @var array The list of routes that contains the callback function and the request method.
10
     */
11
    private $routes;
12
13
    /**
14
     * @var string The path requested for the client(browser, cli, app...).
15
     */
16
    private $path;
17
18
    /**
19
     * @var string The requested method(GET, POST, PUT, DELETE) used by the client.
20
     */
21
    private $requestMethod;
22
23
    /**
24
     * @var object The instance of the router parser.
25
     */
26
    private $routerParser;
27
28
    /**
29
     * @var callable The action to be executed if the any route doesn't match
30
     */
31
    private $notFound;
32
33
    public function __construct() {
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
34
        $this->routes = [];
35
        // only path, without ?, #, etc.
36
        $this->path = (isset($_SERVER['REQUEST_URI'])) ?
37
            rawurldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) : '/';
38
        $this->routerParser = new RouterParser($this->path);
39
        $this->requestMethod = $_SERVER['REQUEST_METHOD'];
40
        $this->overrideMethod();
41
42
        $this->notFound = function() {};
43
    }
44
45
    /**
46
     * Returns the path.
47
     *
48
     * @return string
49
     */
50
51
    public function getPath() {
52
        return $this->path;
53
    }
54
55
    /**
56
     * Returns the array of routes.
57
     *
58
     * @return array
59
     */
60
61
    public function getRoutes() {
62
        return $this->routes;
63
    }
64
65
    /**
66
     * Return the request method used by the client.
67
     *
68
     * @return string
69
     */
70
71
    public function getRequestMethod() {
72
        return $this->requestMethod;
73
    }
74
75
    /**
76
     * Sets the path. Don't use it in production. Only for tests.
77
     *
78
     * @param $path
79
     */
80
81
    public function setPath($path) {
82
        $this->path = $path;
83
        $this->routerParser = new RouterParser($this->path);
84
    }
85
86
    /**
87
     * Set the request method. Used in tests or development.
88
     *
89
     * @param $method
90
     */
91
92
    public function setRequestMethod($method) {
93
        $this->requestMethod = $method;
94
    }
95
96
    /**
97
     * Dispatch to the route set.
98
     *
99
     * @param $method string Method to dispatch
100
     * @param $path string Path to dispatch
101
     */
102
103
    public function dispatch($method, $path) {
104
        $this->setRequestMethod($method);
105
        $this->setPath($path);
106
    }
107
108
    /**
109
     * Searchs in the array of routes the route that matches(same URI
110
     * and request method).
111
     *
112
     * @param $uri string
113
     * @param $method string The request method
114
     * @return bool true if exist a result
115
     */
116
117
    public function find($uri, $method) {
118
        $found = false;
119
        $counter = 0;
120
121
        if(count($this->routes) === 0) {
122
            return $found;
123
        }
124
125
        while($found === false && $counter < count($this->routes)) {
126
            if($this->routes[$counter]->getUri() === $uri && $this->routes[$counter]->getMethod() === $method) {
127
                $found = true;
128
            }
129
130
            $counter++;
131
        }
132
133
        return $found;
134
    }
135
136
    /**
137
     * Adds a new route to the routes array.
138
     *
139
     * @param $uri
140
     * @param $method
141
     * @param $response
142
     * @return bool false if the route has not been added.
143
     */
144
145
    private function addRoute($uri, $method, $response, array $middleware = []) {
146
        if($this->find($uri, $method)) {    // search if exists an apparition
147
            return false;
148
        }
149
150
        $totalRoutes = count($this->routes);
151
        $this->routes[$totalRoutes] = new Order($uri, $method, $response, $middleware);
152
        return true;
153
    }
154
155
    /**
156
     * Get all request headers
157
     *
158
     * @return array The request headers
159
     */
160
    public function getRequestHeaders() {
0 ignored issues
show
Coding Style introduced by
getRequestHeaders uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
161
        // If getallheaders() is available, use that
162
        if(function_exists('getallheaders')) {
163
            return getallheaders();
164
        }
165
        // Method getallheaders() not available: manually extract 'm
166
        $headers = [];
167
        foreach($_SERVER as $key => $value) {
168
            if((substr($key, 0, 5) == 'HTTP_') || ($key == 'CONTENT_TYPE') || ($key == 'CONTENT_LENGTH')) {
169
                $headers[str_replace([' ', 'Http'], ['-', 'HTTP'], ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value;
170
            }
171
        }
172
        return $headers;
173
    }
174
175
    /**
176
     *
177
     */
178
179
    public function overrideMethod() {
0 ignored issues
show
Coding Style introduced by
overrideMethod uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
180
        if($this->requestMethod === 'HEAD') {
181
            $this->requestMethod = 'GET';
182
        } elseif($this->requestMethod === 'POST' && isset($_POST['_method'])) {
183
            $this->requestMethod = $_POST['_method'];
184
        } elseif($this->requestMethod === 'POST') {
185
            $headers = $this->getRequestHeaders();
186
            if(isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], ['PUT', 'DELETE', 'PATCH'])) {
187
                $this->requestMethod = $headers['X-HTTP-Method-Override'];
188
            }
189
        }
190
    }
191
192
    /**
193
     * Clear the array of orders(routes) registered.
194
     */
195
196
    public function clear() {
197
        $this->routes = [];
198
    }
199
200
    /**
201
     * Register the GET request.
202
     *
203
     * @param $uri
204
     * @param $response
205
     */
206
207
    public function get($uri, $response, array $middleware = []) {
208
        $this->addRoute($uri, Method::GET, $response, $middleware);
209
    }
210
211
    /**
212
     * Register the POST request.
213
     *
214
     * @param $uri
215
     * @param $response
216
     */
217
218
    public function post($uri, $response, array $middleware = []) {
219
        $this->addRoute($uri, Method::POST, $response, $middleware);
220
    }
221
222
    /**
223
     * Register the PUT request.
224
     *
225
     * @param $uri
226
     * @param $response
227
     */
228
229
    public function put($uri, $response, array $middleware = []) {
230
        $this->addRoute($uri, Method::PUT, $response, $middleware);
231
    }
232
233
    /**
234
     * Register the DELETE request.
235
     *
236
     * @param $uri string The path requested
237
     * @param $response callable Action to response
238
     */
239
240
    public function delete($uri, $response, array $middleware = []) {
241
        $this->addRoute($uri, Method::DELETE, $response, $middleware);
242
    }
243
244
    /**
245
     * Register the PATCH request.
246
     *
247
     * @param $uri string The path requested
248
     * @param $response callable Action to response
249
     */
250
251
    public function patch($uri, $response, array $middleware = []) {
252
        $this->addRoute($uri, Method::PATCH, $response, $middleware);
253
    }
254
255
    /**
256
     * Register one or more requests for the same uri.
257
     *
258
     * @param $uri string The path requested
259
     * @param $response callable Action to response
260
     * @param $methods mixed Methods to bind. Optional. GET by default
261
     * @param $middleware array Middlewares before and after. Optional
262
     */
263
264
    public function both($uri, $response, $methods = Method::GET, array $middleware = []) {
265
        if(is_array($methods)) {
266
            foreach($methods as $method) {
267
                $this->addRoute($uri, $method, $response, $middleware);
268
            }
269
        } else {
270
            $this->addRoute($uri, $methods, $response, $middleware);
271
        }
272
    }
273
274
    /**
275
     *
276
     */
277
278
    public function any($uri, $response, array $middleware = []) {
279
        $this->addRoute($uri, Method::GET, $response, $middleware);
280
        $this->addRoute($uri, Method::POST, $response, $middleware);
281
        $this->addRoute($uri, Method::PUT, $response, $middleware);
282
        $this->addRoute($uri, Method::DELETE, $response, $middleware);
283
        $this->addRoute($uri, Method::PATCH, $response, $middleware);
284
    }
285
286
    /**
287
     * Sets a callback for the notFound event.
288
     *
289
     * @param $func callable
290
     */
291
292
    public function notFound($func) {
293
        if(is_callable($func)) {
294
            $this->notFound = $func;
295
        }
296
    }
297
298
    /**
299
     * Initialize the router app and start to run
300
     * over the array of routes for any appearance.
301
     * If there is a result then call the callback associate.
302
     * If there is not a result it will execute the notFound
303
     * action.
304
     *
305
     * @return mixed The result of execute the callback.
306
     */
307
308
    public function run() {
309
        $found = false;
310
        $counter = 0;
311
        while($found === false && $counter < count($this->routes)) {
312
            if($this->routerParser->match($this->routes[$counter]->getUri()) && $this->routes[$counter]->getMethod() === $this->requestMethod) {
313
                $found = true;
314
            } else {
315
                $counter++;
316
            }
317
        }
318
319
        if($found) {
320
            // run the before middleware if it exists
321
            if($this->routes[$counter]->hasBefore()) {
322
                call_user_func($this->routes[$counter]->getMiddlewares()['before']);
323
            }
324
325
            $params = $this->routerParser->getParams($this->routes[$counter]->getUri());
326
            $response = call_user_func_array($this->routes[$counter]->getResponse(), $params);
327
328
            // run the after middleware if it exists
329
            if($this->routes[$counter]->hasAfter()) {
330
                call_user_func($this->routes[$counter]->getMiddlewares()['after']);
331
            }
332
333
            return $response;
334
        } else {
335
            return call_user_func($this->notFound);
336
        }
337
    }
338
}