Completed
Push — master ( eda34b...67914c )
by Matthias
02:34
created

Response   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 3
Bugs 2 Features 2
Metric Value
wmc 33
c 3
b 2
f 2
lcom 3
cbo 1
dl 0
loc 345
rs 9.3999

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A hasData() 0 4 1
A setData() 0 4 1
A hasOutputPart() 0 4 1
A setOutputPart() 0 4 1
A getOutputParts() 0 4 1
A getOutput() 0 4 1
A hasOutput() 0 4 1
A setOutput() 0 4 1
A getData() 0 8 2
A getOutputPart() 0 8 2
B redirectTo() 0 24 5
A sendHeader() 0 8 2
A sendHttpStatus() 0 71 3
F buildUrl() 0 45 10
1
<?php
2
/*
3
 * The MIT License (MIT)
4
 *
5
 * Copyright (c) 2015 zepi
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 */
26
27
/**
28
 * The Response holds all information which are outputtet at the end
29
 * of the request.
30
 * 
31
 * @package Zepi\Turbo\Response
32
 * @author Matthias Zobrist <[email protected]>
33
 * @copyright Copyright (c) 2015 zepi
34
 */
35
36
namespace Zepi\Turbo\Response;
37
38
use Zepi\Turbo\Request\RequestAbstract;
39
40
/**
41
 * The Response holds all information which are outputtet at the end
42
 * of the request.
43
 * 
44
 * @author Matthias Zobrist <[email protected]>
45
 * @copyright Copyright (c) 2015 zepi
46
 */
47
class Response
48
{
49
    /**
50
     * @access protected
51
     * @var RequestAbstract
52
     */
53
    protected $_request;
54
    
55
    /**
56
     * @access protected
57
     * @var array
58
     */
59
    protected $_data = array();
60
    
61
    /**
62
     * @access protected
63
     * @var array
64
     */
65
    protected $_outputParts = array();
66
    
67
    /**
68
     * @access protected
69
     * @var string
70
     */
71
    protected $_output;
72
    
73
    /**
74
     * Constructs the object
75
     * 
76
     * @access public
77
     * @param \Zepi\Turbo\Request\RequestAbstract $request
78
     */
79
    public function __construct(RequestAbstract $request)
80
    {
81
        $this->_request = $request;
82
    }
83
    
84
    /**
85
     * Return the data for the given key. If the key does 
86
     * not exists the function will return false.
87
     * 
88
     * @access public
89
     * @param string $key
90
     * @return mixed
91
     */
92
    public function getData($key)
93
    {
94
        if (!$this->hasData($key)) {
95
            return false;
96
        }
97
        
98
        return $this->_data[$key];
99
    }
100
    
101
    /**
102
     * Returns true if the given key is set.
103
     * 
104
     * @access public
105
     * @param string $key
106
     * @return boolean
107
     */
108
    public function hasData($key)
109
    {
110
        return (isset($this->_data[$key]));
111
    }
112
    
113
    /**
114
     * Saves the value for the given key in the response object.
115
     * 
116
     * @access public
117
     * @param string $key
118
     * @param mixed $value
119
     */
120
    public function setData($key, $value)
121
    {
122
        $this->_data[$key] = $value;
123
    }
124
    
125
    /**
126
     * Returns the output for the given key. If the key does
127
     * not exists the function will return false.
128
     * 
129
     * @access public
130
     * @param string $key
131
     * @return false|string
132
     */
133
    public function getOutputPart($key)
134
    {
135
        if (!$this->hasOutputPart($key)) {
136
            return false;
137
        }
138
        
139
        return $this->_outputParts[$key];
140
    }
141
    
142
    /**
143
     * Returns true if the given key exists as output key.
144
     * 
145
     * @access public
146
     * @param string $key
147
     * @return boolean
148
     */
149
    public function hasOutputPart($key)
150
    {
151
        return (isset($this->_outputParts[$key]));
152
    }
153
    
154
    /**
155
     * Saves the output for the given key in the Response object.
156
     * 
157
     * @access public
158
     * @param string $key
159
     * @param string $output
160
     */
161
    public function setOutputPart($key, $output)
162
    {
163
        $this->_outputParts[$key] = $output;
164
    }
165
    
166
    /**
167
     * Returns all output parts of the Response object.
168
     * 
169
     * @access public
170
     * @return array
171
     */
172
    public function getOutputParts()
173
    {
174
        return $this->_outputParts;
175
    }
176
    
177
    /**
178
     * Returns the output of the response.
179
     * 
180
     * @access public
181
     * @return string
182
     */
183
    public function getOutput()
184
    {
185
        return $this->_output;
186
    }
187
    
188
    /**
189
     * Returns true if the response has an output.
190
     * 
191
     * @access public
192
     * @return boolean
193
     */
194
    public function hasOutput()
195
    {
196
        return ($this->_output != '');
197
    }
198
    
199
    /**
200
     * Sets the output of the response.
201
     * 
202
     * @access public
203
     * @param string $output
204
     */
205
    public function setOutput($output)
206
    {
207
        $this->_output = $output;
208
    }
209
    
210
    /**
211
     * Set the Location header to redirect a request
212
     * 
213
     * @access public
214
     * @param string $target
215
     * @param integer $headerCode
216
     * @param boolean $withOrigin
217
     */
218
    public function redirectTo($target, $headerCode = 301, $withOrigin = false)
219
    {
220
        if (strpos($target, 'http://') === false) {
221
            $target = $this->_request->getFullRoute($target);
222
        }
223
        
224
        if ($withOrigin) {
225
            $origin = $this->_request->getFullRoute();
226
            $additionalQuery = '_origin=' . base64_encode($origin);
227
            
228
            $parts = parse_url($target);
229
            
230
            if (!isset($parts['query'])) {
231
                $parts['query'] = '';
232
            } else if ($parts['query'] !== '') {
233
                $parts['query'] .= '&';
234
            }
235
            
236
            $parts['query'] .= $additionalQuery;
237
            $target = $this->buildUrl($parts);
0 ignored issues
show
Security Bug introduced by
It seems like $parts defined by parse_url($target) on line 228 can also be of type false; however, Zepi\Turbo\Response\Response::buildUrl() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
238
        }
239
        
240
        header("Location: " . $target, true, $headerCode);
241
    }
242
    
243
    /**
244
     * Sends a header
245
     * 
246
     * @access public
247
     * @param string $message
248
     * @param integer $code
249
     */
250
    public function sendHeader($message, $code = null)
251
    {
252
        if ($code !== null) {
253
            header($message, true, $code);
254
        } else {
255
            header($message);
256
        }
257
    }
258
    
259
    /**
260
     * Sends the status code for the give code
261
     * 
262
     * @access public
263
     * @param integer $code
264
     * @param boolean $resetOutput
265
     */
266
    public function sendHttpStatus($code, $resetOutput = false)
267
    {
268
        $codes = array(
269
            100 => 'Continue',
270
            101 => 'Switching Protocols',
271
            102 => 'Processing',
272
            200 => 'OK',
273
            201 => 'Created',
274
            202 => 'Accepted',
275
            203 => 'Non-Authoritative Information',
276
            204 => 'No Content',
277
            205 => 'Reset Content',
278
            206 => 'Partial Content',
279
            207 => 'Multi-Status',
280
            300 => 'Multiple Choices',
281
            301 => 'Moved Permanently',
282
            302 => 'Found',
283
            303 => 'See Other',
284
            304 => 'Not Modified',
285
            305 => 'Use Proxy',
286
            306 => 'Switch Proxy',
287
            307 => 'Temporary Redirect',
288
            400 => 'Bad Request',
289
            401 => 'Unauthorized',
290
            402 => 'Payment Required',
291
            403 => 'Forbidden',
292
            404 => 'Not Found',
293
            405 => 'Method Not Allowed',
294
            406 => 'Not Acceptable',
295
            407 => 'Proxy Authentication Required',
296
            408 => 'Request Timeout',
297
            409 => 'Conflict',
298
            410 => 'Gone',
299
            411 => 'Length Required',
300
            412 => 'Precondition Failed',
301
            413 => 'Request Entity Too Large',
302
            414 => 'Request-URI Too Long',
303
            415 => 'Unsupported Media Type',
304
            416 => 'Requested Range Not Satisfiable',
305
            417 => 'Expectation Failed',
306
            418 => 'I\'m a teapot',
307
            422 => 'Unprocessable Entity',
308
            423 => 'Locked',
309
            424 => 'Failed Dependency',
310
            425 => 'Unordered Collection',
311
            426 => 'Upgrade Required',
312
            449 => 'Retry With',
313
            450 => 'Blocked by Windows Parental Controls',
314
            500 => 'Internal Server Error',
315
            501 => 'Not Implemented',
316
            502 => 'Bad Gateway',
317
            503 => 'Service Unavailable',
318
            504 => 'Gateway Timeout',
319
            505 => 'HTTP Version Not Supported',
320
            506 => 'Variant Also Negotiates',
321
            507 => 'Insufficient Storage',
322
            509 => 'Bandwidth Limit Exceeded',
323
            510 => 'Not Extended'
324
        );
325
        
326
        if (!isset($codes[$code])) {
327
            return false;
328
        }
329
        
330
        $message = $this->_request->getProtocol() . ' ' . $code . ' ' . $codes[$code];
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Zepi\Turbo\Request\RequestAbstract as the method getProtocol() does only exist in the following sub-classes of Zepi\Turbo\Request\RequestAbstract: Zepi\Turbo\Request\WebRequest. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
331
        header($message, true, $code);
332
        
333
        if ($resetOutput) {
334
            $this->setOutput($message);
335
        }
336
    }
337
    
338
    /**
339
     * Returns a full url for the given url parts array
340
     * from the function `parse_url()`.
341
     * 
342
     * @access public
343
     * @param array $urlParts
344
     * @return string
345
     */
346
    public function buildUrl($urlParts)
347
    {
348
        $url = '';
349
        $auth = false;
350
        
351
        if (isset($urlParts['scheme'])) {
352
            $url .= $urlParts['scheme'] . '://';
353
        }
354
        
355
        if (isset($urlParts['user'])) {
356
            $url .= $urlParts['user'];
357
            $auth = true;
358
        }
359
        
360
        if (isset($urlParts['pass'])) {
361
            $url .= ':' . $urlParts['pass'];
362
            $auth = true;
363
        }
364
        
365
        if ($auth) {
366
            $url .= '@';
367
        }
368
        
369
        if (isset($urlParts['host'])) {
370
            $url .= $urlParts['host'];
371
        }
372
        
373
        if (isset($urlParts['port'])) {
374
            $url .= ':' . $urlParts['port'];
375
        }
376
        
377
        if (isset($urlParts['path'])) {
378
            $url .= $urlParts['path'];
379
        }
380
        
381
        if (isset($urlParts['query'])) {
382
            $url .= '?' . $urlParts['query'];
383
        }
384
        
385
        if (isset($urlParts['fragment'])) {
386
            $url .= '#' . $urlParts['fragment'];
387
        }
388
        
389
        return $url;
390
    }
391
}
392