Router::getURI()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.2
cc 4
eloc 10
nc 6
nop 0
crap 4
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 39
    public function __construct(Config $config)
50
    {
51 39
        $this->routes = $config->get('Routes');
52 39
        $this->prepare();
53 30
    }
54
55
    /**
56
     * Set class defaults and normalized url/uri segments
57
     */
58 39
    private function prepare()
59
    {
60 39
        $this->uri = $this->getURI();
61 39
        $this->discoverRoute();
62 39
        $this->isURIClean();
63 30
        $this->currentURL = $this->baseURL($this->uri);
64 30
        $this->baseURL = $this->baseURL();
65 30
    }
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 39
    private function isURIClean()
74
    {
75 39
        if ($this->uri !== '' && !preg_match("/^[a-z0-9:_\/\.\[\]-]+$/i", $this->uri)) {
76 9
            throw new RouteException('Disallowed characters');
77
        }
78 30
    }
79
80
    /**
81
     * Normalize the $_SERVER vars for formatting the URI.
82
     *
83
     * @access private
84
     * @return string formatted/u/r/l
85
     */
86 39
    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 39
        if (!empty($_SERVER['PATH_INFO'])) {
89 1
            $uri = $_SERVER['PATH_INFO'];
90 38
        } elseif (!empty($_SERVER['REQUEST_URI'])) {
91 26
            $uri = $_SERVER['REQUEST_URI'];
92
        } else {
93 12
            $uri = false;
94
        }
95
96 39
        if ($uri === '/') {
97 1
            $uri = false;
98
        }
99
100 39
        return trim(preg_replace('/\?.*/', '', $uri), '/');
101
    }
102
103 6
    public function getAction()
104
    {
105 6
        return $this->action;
106
    }
107
108 11
    public function getParameters()
109
    {
110 11
        return $this->params;
111
    }
112
113
    //@TODO Normalize parameters.
114 39
    private function discoverRoute()
115
    {
116 39
        $routes = $this->routes;
117 39
        $params = [];
118
119
120 39
        foreach ($routes as $route => $action) {
121 39
            $pattern = '/^(?i)' . str_replace('/', '\/', $route) . '$/';
122
            // normalize these parameters
123 39
            if (preg_match($pattern, $this->uri, $params)) {
124 16
                array_shift($params);
125 16
                $this->action = $action;
126 39
                $this->params = $params;
127
            }
128
        }
129 39
    }
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 1
            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 30
    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 30
        if (is_null($this->baseURL)) {
170 30
            $self = $_SERVER['PHP_SELF'];
171 30
            $server = $_SERVER['HTTP_HOST']
172 30
                      . rtrim(str_replace(strstr($self, 'index.php'), '', $self), '/');
173
174 30
            if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
175 1
                || !empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
176 30
                && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'
177
            ) {
178 29
                $protocol = 'https://';
179
            } else {
180 1
                $protocol = 'http://';
181
            }
182
183 30
            $this->baseURL = $protocol . $server;
184
        }
185
186 30
        $url = $this->baseURL;
187
188 30
        if ($path !== '') {
189 17
            $url .= '/' . $path;
190
        }
191
192 30
        return $url;
193
    }
194
}
195