Completed
Push — master ( 3fea7f...2576ae )
by Josh
72:58 queued 63:12
created

Router::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
declare(strict_types=1);
3
namespace Zewa;
4
5
use Zewa\Exception\RouteException;
6
7
/**
8
 * Handles everything relating to URL/URI.
9
 *
10
 * @author Zechariah Walden<zech @ zewadesign.com>
11
 */
12
class Router
13
{
14
    /**
15
     * System routes
16
     *
17
     * @var object
18
     */
19
    private $routes;
20
21
    /**
22
     * The base URL
23
     *
24
     * @var    string
25
     * @access public
26
     */
27
    public $baseURL;
28
29
    public $currentURL;
30
31
    /**
32
     * Default uri
33
     *
34
     * @var    string
35
     * @access public
36
     */
37
    public $uri;
38
39
    /**
40
     * @var array
41
     */
42
    public $params = [];
43
44
    public $action;
45
46
    /**
47
     * Load up some basic configuration settings.
48
     */
49 15
    public function __construct(Config $config)
50
    {
51 15
        $this->routes = $config->get('Routes');
52 15
        $this->prepare();
53 6
    }
54
55
    /**
56
     * Set class defaults and normalized url/uri segments
57
     */
58 15
    private function prepare()
59
    {
60 15
        $this->uri = $this->getURI();
61 15
        $this->discoverRoute();
62 15
        $this->isURIClean();
63 6
        $this->currentURL = $this->baseURL($this->uri);
64 6
        $this->baseURL = $this->baseURL();
65 6
    }
66
67
    /**
68
     * Checks if URL contains special characters not permissable/considered dangerous
69
     *
70
     * Safe: a-z, 0-9, :, _, [, ], +
71
     * @throws RouteException
72
     */
73 15
    private function isURIClean()
74
    {
75 15
        if ($this->uri !== '' && !preg_match("/^[a-z0-9:_\/\.\[\]-]+$/i", $this->uri)) {
76 9
            throw new RouteException('Disallowed characters');
77
        }
78 6
    }
79
80
    /**
81
     * Normalize the $_SERVER vars for formatting the URI.
82
     *
83
     * @access private
84
     * @return string formatted/u/r/l
85
     */
86 15
    private function getURI()
0 ignored issues
show
Coding Style introduced by
getURI 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...
87
    {
88 15
        if (!empty($_SERVER['PATH_INFO'])) {
89
            $uri = $_SERVER['PATH_INFO'];
90 15
        } elseif (!empty($_SERVER['REQUEST_URI'])) {
91 15
            $uri = $_SERVER['REQUEST_URI'];
92
        } else {
93
            $uri = false;
94
        }
95
96 15
        if ($uri === '/') {
97
            $uri = false;
98
        }
99
100 15
        return trim(preg_replace('/\?.*/', '', $uri), '/');
101
    }
102
103
    public function getAction()
104
    {
105
        return $this->action;
106
    }
107
108 6
    public function getParameters()
109
    {
110 6
        return $this->params;
111
    }
112
113
    //@TODO Normalize parameters.
114 15
    private function discoverRoute()
115
    {
116 15
        $routes = $this->routes;
117 15
        $params = [];
118
119
120 15
        foreach ($routes as $route => $action) {
121 15
            $pattern = '/^(?i)' . str_replace('/', '\/', $route) . '$/';
122
            // normalize these parameters
123 15
            if (preg_match($pattern, $this->uri, $params)) {
124 6
                array_shift($params);
125 6
                $this->action = $action;
126 15
                $this->params = $params;
127
            }
128
        }
129 15
    }
130
131
    private function addQueryString($url, $key, $value)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
132
    {
133
        $url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
134
        $url = substr($url, 0, -1);
135
        if (strpos($url, '?') === false) {
136
            return ($url . '?' . $key . '=' . $value);
137
        } else {
138
            return ($url . '&' . $key . '=' . $value);
139
        }
140
    }
141
142
    private function removeQueryString($url, $key)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
143
    {
144
        $url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
145
        $url = substr($url, 0, -1);
146
        return ($url);
147
    }
148
149
    /**
150
     * Return the currentURL w/ query strings
151
     *
152
     * @access public
153
     * @return string http://tld.com/formatted/u/r/l?q=bingo
154
     */
155
    public function currentURL($params = false)
0 ignored issues
show
Coding Style introduced by
currentURL 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...
Unused Code introduced by
The parameter $params is not used and could be removed.

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

Loading history...
156
    {
157
        return $this->currentURL . (empty($_SERVER['QUERY_STRING'])?:'?' . $_SERVER['QUERY_STRING']);
158
    }
159
160
    /**
161
     * Return the baseURL
162
     *
163
     * @access public
164
     * @return string http://tld.com
165
     */
166 6
    public function baseURL($path = '')
0 ignored issues
show
Coding Style introduced by
baseURL 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...
167
    {
168 6
        if (is_null($this->baseURL)) {
169 6
            $self = $_SERVER['PHP_SELF'];
170 6
            $server = $_SERVER['HTTP_HOST']
171 6
                      . rtrim(str_replace(strstr($self, 'index.php'), '', $self), '/');
172
173 6
            if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
174 6
                || !empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
175 6
                && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'
176
            ) {
177
                $protocol = 'https://';
178
            } else {
179 6
                $protocol = 'http://';
180
            }
181
182 6
            $this->baseURL = $protocol . $server;
183
        }
184
185 6
        $url = $this->baseURL;
186
187 6
        if ($path !== '') {
188 6
            $url .= '/' . $path;
189
        }
190
191 6
        return $url;
192
    }
193
194
    /**
195
     * Set optional status header, and redirect to provided URL
196
     *
197
     * @access public
198
     * @return bool
199
     */
200
    public function redirect($url = '/', $status = null)
201
    {
202
        $url = str_replace(array('\r', '\n', '%0d', '%0a'), '', $url);
203
204
        if (headers_sent()) {
205
            return false;
206
        }
207
208
        // trap session vars before redirect
209
        session_write_close();
210
211
        if (is_null($status)) {
212
            $status = '302';
213
        }
214
215
        // push a status to the browser if necessary
216
        if ((int)$status > 0) {
217
            switch ($status) {
218
                case '301':
219
                    $msg = '301 Moved Permanently';
220
                    break;
221
                case '307':
222
                    $msg = '307 Temporary Redirect';
223
                    break;
224
                // Using these below (except 302) would be an intentional misuse of the 'system'
225
                // Need to dig into the above comment @zech
226
                case '401':
227
                    $msg = '401 Access Denied';
228
                    break;
229
                case '403':
230
                    $msg = '403 Request Forbidden';
231
                    break;
232
                case '404':
233
                    $msg = '404 Not Found';
234
                    break;
235
                case '405':
236
                    $msg = '405 Method Not Allowed';
237
                    break;
238
                case '302':
239
                default:
240
                    $msg = '302 Found';
241
                    break; // temp redirect
242
            }
243
            if (isset($msg)) {
244
                header('HTTP/1.1 ' . $msg);
245
            }
246
        }
247
248
        $url = preg_replace('!^/*!', '', $url);
249
        header("Location: " . $url);
250
    }
251
}
252