Completed
Push — master ( 536152...0ce86f )
by
unknown
33s
created

Router::getAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
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 23
    public function __construct(Config $config)
50
    {
51 23
        $this->routes = $config->get('Routes');
52 23
        $this->prepare();
53 14
    }
54
55
    /**
56
     * Set class defaults and normalized url/uri segments
57
     */
58 23
    private function prepare()
59
    {
60 23
        $this->uri = $this->getURI();
61 23
        $this->discoverRoute();
62 23
        $this->isURIClean();
63 14
        $this->currentURL = $this->baseURL($this->uri);
64 14
        $this->baseURL = $this->baseURL();
65 14
    }
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 23
    private function isURIClean()
74
    {
75 23
        if ($this->uri !== '' && !preg_match("/^[a-z0-9:_\/\.\[\]-]+$/i", $this->uri)) {
76 9
            throw new RouteException('Disallowed characters');
77
        }
78 14
    }
79
80
    /**
81
     * Normalize the $_SERVER vars for formatting the URI.
82
     *
83
     * @access private
84
     * @return string formatted/u/r/l
85
     */
86 23
    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 23
        if (!empty($_SERVER['PATH_INFO'])) {
89 1
            $uri = $_SERVER['PATH_INFO'];
90 22
        } elseif (!empty($_SERVER['REQUEST_URI'])) {
91 21
            $uri = $_SERVER['REQUEST_URI'];
92
        } else {
93 1
            $uri = false;
94
        }
95
96 23
        if ($uri === '/') {
97 1
            $uri = false;
98
        }
99
100 23
        return trim(preg_replace('/\?.*/', '', $uri), '/');
101
    }
102
103 1
    public function getAction()
104
    {
105 1
        return $this->action;
106
    }
107
108 6
    public function getParameters()
109
    {
110 6
        return $this->params;
111
    }
112
113
    //@TODO Normalize parameters.
114 23
    private function discoverRoute()
115
    {
116 23
        $routes = $this->routes;
117 23
        $params = [];
118
119
120 23
        foreach ($routes as $route => $action) {
121 23
            $pattern = '/^(?i)' . str_replace('/', '\/', $route) . '$/';
122
            // normalize these parameters
123 23
            if (preg_match($pattern, $this->uri, $params)) {
124 12
                array_shift($params);
125 12
                $this->action = $action;
126 23
                $this->params = $params;
127
            }
128
        }
129 23
    }
130
131 2
    public function addQueryString($url, $key, $value)
132
    {
133 2
        $url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
134 2
        $url = substr($url, 0, -1);
135 2
        if (strpos($url, '?') === false) {
136 2
            return ($url . '?' . $key . '=' . $value);
137
        } else {
138
            return ($url . '&' . $key . '=' . $value);
139
        }
140
    }
141
142 1
    public function removeQueryString($url, $key)
143
    {
144 1
        $url = preg_replace('/(.*)(\?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
145 1
        $url = substr($url, 0, -1);
146 1
        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 2
    public function currentURL()
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...
156
    {
157 2
        $queryString = empty($_SERVER['QUERY_STRING']) === true ? "" : '?' . $_SERVER['QUERY_STRING'];
158 2
        return $this->currentURL . $queryString;
159
    }
160
161
    /**
162
     * Return the baseURL
163
     *
164
     * @access public
165
     * @return string http://tld.com
166
     */
167 14
    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...
168
    {
169 14
        if (is_null($this->baseURL)) {
170 14
            $self = $_SERVER['PHP_SELF'];
171 14
            $server = $_SERVER['HTTP_HOST']
172 14
                      . rtrim(str_replace(strstr($self, 'index.php'), '', $self), '/');
173
174 14
            if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
175 9
                || !empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
176 14
                && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'
177
            ) {
178 5
                $protocol = 'https://';
179
            } else {
180 9
                $protocol = 'http://';
181
            }
182
183 14
            $this->baseURL = $protocol . $server;
184
        }
185
186 14
        $url = $this->baseURL;
187
188 14
        if ($path !== '') {
189 12
            $url .= '/' . $path;
190
        }
191
192 14
        return $url;
193
    }
194
195
    /**
196
     * Set optional status header, and redirect to provided URL
197
     *
198
     * @access public
199
     * @return bool
200
     */
201
    public function redirect($url = '/', $status = null)
202
    {
203
        $url = str_replace(array('\r', '\n', '%0d', '%0a'), '', $url);
204
205
        if (headers_sent()) {
206
            return false;
207
        }
208
209
        // trap session vars before redirect
210
        session_write_close();
211
212
        if (is_null($status)) {
213
            $status = '302';
214
        }
215
216
        // push a status to the browser if necessary
217
        if ((int)$status > 0) {
218
            switch ($status) {
219
                case '301':
220
                    $msg = '301 Moved Permanently';
221
                    break;
222
                case '307':
223
                    $msg = '307 Temporary Redirect';
224
                    break;
225
                // Using these below (except 302) would be an intentional misuse of the 'system'
226
                // Need to dig into the above comment @zech
227
                case '401':
228
                    $msg = '401 Access Denied';
229
                    break;
230
                case '403':
231
                    $msg = '403 Request Forbidden';
232
                    break;
233
                case '404':
234
                    $msg = '404 Not Found';
235
                    break;
236
                case '405':
237
                    $msg = '405 Method Not Allowed';
238
                    break;
239
                case '302':
240
                default:
241
                    $msg = '302 Found';
242
                    break; // temp redirect
243
            }
244
            if (isset($msg)) {
245
                header('HTTP/1.1 ' . $msg);
246
            }
247
        }
248
249
        $url = preg_replace('!^/*!', '', $url);
250
        header("Location: " . $url);
251
    }
252
}
253