Request   B
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 346
rs 7.4757
c 0
b 0
f 0
wmc 53
lcom 5
cbo 2

26 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 9 6
A setParams() 0 4 1
A getStringParam() 0 4 2
A getIntegerParam() 0 4 3
A getValue() 0 6 2
A getValues() 0 8 2
A checkCSRFToken() 0 6 2
A getBody() 0 4 1
A getJson() 0 4 2
A getFileContent() 0 8 2
A getFilePath() 0 4 2
A getFileInfo() 0 4 2
A getMethod() 0 4 1
A isPost() 0 4 1
A isAjax() 0 4 1
A isHTTPS() 0 8 3
A getCookie() 0 4 2
A getHeader() 0 6 1
A getRemoteUser() 0 4 1
A getQueryString() 0 4 1
A getUri() 0 4 1
A getUserAgent() 0 4 2
B getIpAddress() 0 23 4
A getStartTime() 0 4 2
A getServerVariable() 0 4 2
A checkCSRFParam() 0 10 4

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of Jitamin.
5
 *
6
 * Copyright (C) Jitamin Team
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Jitamin\Foundation\Http;
13
14
use Jitamin\Foundation\Base;
15
use Jitamin\Foundation\Exceptions\AccessForbiddenException;
16
use Pimple\Container;
17
18
/**
19
 * Request class.
20
 */
21
class Request extends Base
22
{
23
    /**
24
     * Pointer to PHP environment variables.
25
     *
26
     * @var array
27
     */
28
    private $server;
29
    private $get;
30
    private $post;
31
    private $files;
32
    private $cookies;
33
34
    /**
35
     * Constructor.
36
     *
37
     * @param \Pimple\Container $container
38
     * @param array             $server
39
     * @param array             $get
40
     * @param array             $post
41
     * @param array             $files
42
     * @param array             $cookies
43
     */
44
    public function __construct(Container $container, array $server = [], array $get = [], array $post = [], array $files = [], array $cookies = [])
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...
Coding Style introduced by
__construct uses the super-global variable $_GET 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...
Coding Style introduced by
__construct 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...
Coding Style introduced by
__construct uses the super-global variable $_FILES 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...
Coding Style introduced by
__construct uses the super-global variable $_COOKIE 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...
45
    {
46
        parent::__construct($container);
47
        $this->server = empty($server) ? $_SERVER : $server;
48
        $this->get = empty($get) ? $_GET : $get;
49
        $this->post = empty($post) ? $_POST : $post;
50
        $this->files = empty($files) ? $_FILES : $files;
51
        $this->cookies = empty($cookies) ? $_COOKIE : $cookies;
52
    }
53
54
    /**
55
     * Set GET parameters.
56
     *
57
     * @param array $params
58
     */
59
    public function setParams(array $params)
60
    {
61
        $this->get = array_merge($this->get, $params);
62
    }
63
64
    /**
65
     * Get query string string parameter.
66
     *
67
     * @param string $name          Parameter name
68
     * @param string $default_value Default value
69
     *
70
     * @return string
71
     */
72
    public function getStringParam($name, $default_value = '')
73
    {
74
        return isset($this->get[$name]) ? $this->get[$name] : $default_value;
75
    }
76
77
    /**
78
     * Get query string integer parameter.
79
     *
80
     * @param string $name          Parameter name
81
     * @param int    $default_value Default value
82
     *
83
     * @return int
84
     */
85
    public function getIntegerParam($name, $default_value = 0)
86
    {
87
        return isset($this->get[$name]) && ctype_digit($this->get[$name]) ? (int) $this->get[$name] : $default_value;
88
    }
89
90
    /**
91
     * Get a form value.
92
     *
93
     * @param string $name Form field name
94
     *
95
     * @return string|null
96
     */
97
    public function getValue($name)
98
    {
99
        $values = $this->getValues();
100
101
        return isset($values[$name]) ? $values[$name] : null;
102
    }
103
104
    /**
105
     * Get form values and check for CSRF token.
106
     *
107
     * @return array
108
     */
109
    public function getValues()
110
    {
111
        if ($this->checkCSRFParam()) {
112
            return $this->post;
113
        }
114
115
        return [];
116
    }
117
118
    /**
119
     * Check for CSRF token.
120
     *
121
     * @return void
122
     */
123
    public function checkCSRFToken()
124
    {
125
        if (!$this->checkCSRFParam()) {
126
            throw new AccessForbiddenException();
127
        }
128
    }
129
130
    /**
131
     * Get the raw body of the HTTP request.
132
     *
133
     * @return string
134
     */
135
    public function getBody()
136
    {
137
        return file_get_contents('php://input');
138
    }
139
140
    /**
141
     * Get the Json request body.
142
     *
143
     * @return array
144
     */
145
    public function getJson()
146
    {
147
        return json_decode($this->getBody(), true) ?: [];
148
    }
149
150
    /**
151
     * Get the content of an uploaded file.
152
     *
153
     * @param string $name Form file name
154
     *
155
     * @return string
156
     */
157
    public function getFileContent($name)
158
    {
159
        if (isset($this->files[$name]['tmp_name'])) {
160
            return file_get_contents($this->files[$name]['tmp_name']);
161
        }
162
163
        return '';
164
    }
165
166
    /**
167
     * Get the path of an uploaded file.
168
     *
169
     * @param string $name Form file name
170
     *
171
     * @return string
172
     */
173
    public function getFilePath($name)
174
    {
175
        return isset($this->files[$name]['tmp_name']) ? $this->files[$name]['tmp_name'] : '';
176
    }
177
178
    /**
179
     * Get info of an uploaded file.
180
     *
181
     * @param string $name Form file name
182
     *
183
     * @return array
184
     */
185
    public function getFileInfo($name)
186
    {
187
        return isset($this->files[$name]) ? $this->files[$name] : [];
188
    }
189
190
    /**
191
     * Return HTTP method.
192
     *
193
     * @return bool
194
     */
195
    public function getMethod()
196
    {
197
        return $this->getServerVariable('REQUEST_METHOD');
198
    }
199
200
    /**
201
     * Return true if the HTTP request is sent with the POST method.
202
     *
203
     * @return bool
204
     */
205
    public function isPost()
206
    {
207
        return $this->getServerVariable('REQUEST_METHOD') === 'POST';
208
    }
209
210
    /**
211
     * Return true if the HTTP request is an Ajax request.
212
     *
213
     * @return bool
214
     */
215
    public function isAjax()
216
    {
217
        return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
218
    }
219
220
    /**
221
     * Check if the page is requested through HTTPS.
222
     *
223
     * Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS
224
     *
225
     * @return bool
226
     */
227
    public function isHTTPS()
228
    {
229
        if ($this->getServerVariable('HTTP_X_FORWARDED_PROTO') === 'https') {
230
            return true;
231
        }
232
233
        return $this->getServerVariable('HTTPS') !== '' && $this->server['HTTPS'] !== 'off';
234
    }
235
236
    /**
237
     * Get cookie value.
238
     *
239
     * @param string $name
240
     *
241
     * @return string
242
     */
243
    public function getCookie($name)
244
    {
245
        return isset($this->cookies[$name]) ? $this->cookies[$name] : '';
246
    }
247
248
    /**
249
     * Return a HTTP header value.
250
     *
251
     * @param string $name Header name
252
     *
253
     * @return string
254
     */
255
    public function getHeader($name)
256
    {
257
        $name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
258
259
        return $this->getServerVariable($name);
260
    }
261
262
    /**
263
     * Get remote user.
264
     *
265
     * @return string
266
     */
267
    public function getRemoteUser()
268
    {
269
        return $this->getServerVariable(REVERSE_PROXY_USER_HEADER);
270
    }
271
272
    /**
273
     * Returns query string.
274
     *
275
     * @return string
276
     */
277
    public function getQueryString()
278
    {
279
        return $this->getServerVariable('QUERY_STRING');
280
    }
281
282
    /**
283
     * Return URI.
284
     *
285
     * @return string
286
     */
287
    public function getUri()
288
    {
289
        return $this->getServerVariable('REQUEST_URI');
290
    }
291
292
    /**
293
     * Get the user agent.
294
     *
295
     * @return string
296
     */
297
    public function getUserAgent()
298
    {
299
        return empty($this->server['HTTP_USER_AGENT']) ? t('Unknown') : $this->server['HTTP_USER_AGENT'];
300
    }
301
302
    /**
303
     * Get the IP address of the user.
304
     *
305
     * @return string
306
     */
307
    public function getIpAddress()
308
    {
309
        $keys = [
310
            'HTTP_X_REAL_IP',
311
            'HTTP_CLIENT_IP',
312
            'HTTP_X_FORWARDED_FOR',
313
            'HTTP_X_FORWARDED',
314
            'HTTP_X_CLUSTER_CLIENT_IP',
315
            'HTTP_FORWARDED_FOR',
316
            'HTTP_FORWARDED',
317
            'REMOTE_ADDR',
318
        ];
319
320
        foreach ($keys as $key) {
321
            if ($this->getServerVariable($key) !== '') {
322
                foreach (explode(',', $this->server[$key]) as $ipAddress) {
323
                    return trim($ipAddress);
324
                }
325
            }
326
        }
327
328
        return t('Unknown');
329
    }
330
331
    /**
332
     * Get start time.
333
     *
334
     * @return float
335
     */
336
    public function getStartTime()
337
    {
338
        return $this->getServerVariable('REQUEST_TIME_FLOAT') ?: 0;
339
    }
340
341
    /**
342
     * Get server variable.
343
     *
344
     * @param string $variable
345
     *
346
     * @return string
347
     */
348
    public function getServerVariable($variable)
349
    {
350
        return isset($this->server[$variable]) ? $this->server[$variable] : '';
351
    }
352
353
    /**
354
     * Check if the CSRF token from the URL is correct.
355
     */
356
    protected function checkCSRFParam()
357
    {
358
        if (!empty($this->post) && !empty($this->post['csrf_token']) && $this->token->validateCSRFToken($this->post['csrf_token'])) {
0 ignored issues
show
Documentation introduced by
The property token does not exist on object<Jitamin\Foundation\Http\Request>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
359
            unset($this->post['csrf_token']);
360
361
            return true;
362
        }
363
364
        return false;
365
    }
366
}
367