Completed
Pull Request — master (#363)
by Anton
05:32
created

Response::addHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
crap 2
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link https://github.com/bluzphp/framework
7
 */
8
9
/**
10
 * @namespace
11
 */
12
namespace Bluz\Response;
13
14
use Bluz\Application\Exception\NotAcceptableException;
15
use Bluz\Cli\CliResponse;
16
use Bluz\Common\Options;
17
use Bluz\Controller\Controller;
18
use Bluz\Layout\Layout;
19
use Bluz\Proxy\Messages;
20
use Bluz\Proxy\Request;
21
use Zend\Diactoros\Response\EmptyResponse;
22
use Zend\Diactoros\Response\HtmlResponse;
23
use Zend\Diactoros\Response\JsonResponse;
24
use Zend\Diactoros\Response\RedirectResponse;
25
use Zend\Diactoros\Response\SapiEmitter;
26
27
/**
28
 * Response Container
29
 *
30
 * @package  Bluz\Response
31
 * @author   Anton Shevchuk
32
 * @link     https://github.com/bluzphp/framework/wiki/Response
33
 */
34
class Response
35
{
36
    use Options;
37
38
    /**
39
     * @var string HTTP protocol version
40
     */
41
    protected $protocol = '1.1';
42
43
    /**
44
     * @var integer response code equal to HTTP status codes
45
     */
46
    protected $code = 200;
47
48
    /**
49
     * @var string|null HTTP Phrase
50
     */
51
    protected $phrase;
52
53
    /**
54
     * @var array list of headers
55
     */
56
    protected $headers = array();
57
58
    /**
59
     * @var array list of cookies
60
     */
61
    protected $cookies = array();
62
63
    /**
64
     * @var mixed result can be Controller|Layout
65
     */
66
    protected $body;
67
68
    /**
69
     * @var string CLI|HTML|JSON
70
     */
71
    protected $type = 'HTML';
72
73
    /**
74
     * send
75
     *
76
     * @throws NotAcceptableException
77
     */
78
    public function send()
79
    {
80
        $body = $this->getBody();
81
82
        $this->sendCookies();
83
        
84
        switch (true) {
85
            case 'CLI' == $this->type:
86
                // CLI response
87
                $response = new CliResponse(
88
                    $body->render('CLI'),
0 ignored issues
show
Unused Code introduced by
The call to Layout::render() has too many arguments starting with 'CLI'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
89
                    $this->getStatusCode()
90
                );
91
                break;
92
            case is_null($body):
93
            case 204 == $this->getStatusCode():
94
                $response = new EmptyResponse($this->getStatusCode(), $this->getHeaders());
95
                break;
96
            case 301 == $this->getStatusCode():
97
            case 302 == $this->getStatusCode():
98
                $response = new RedirectResponse(
99
                    $this->getHeader('Location'),
100
                    $this->getStatusCode(),
101
                    $this->getHeaders()
102
                );
103
                break;
104
            case 'JSON' == $this->type:
105
                // JSON response
106
                // setup messages
107
                if (Messages::count()) {
108
                    $this->setHeader('Bluz-Notify', json_encode(Messages::popAll()));
109
                }
110
111
                // encode body data to JSON
112
                $response = new JsonResponse(
113
                    $body,
114
                    $this->getStatusCode(),
115
                    $this->getHeaders()
116
                );
117
                break;
118
            case 'HTML' == $this->type:
119
            default:
120
                // HTML response
121
                $response = new HtmlResponse(
122
                    (string) $body,
123
                    $this->getStatusCode(),
124
                    $this->getHeaders()
125
                );
126
                break;
127
        }
128
129
        $emitter = new SapiEmitter();
130
        $emitter->emit($response);
131
    }
132
133
    /**
134
     * Set Response Type, one of JSON, HTML or CLI
135
     *
136
     * @param $type
137
     */
138
    public function switchType($type)
139
    {
140
        // switch statement for $type
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
141
        switch ($type) {
142
            case 'JSON':
143
                $this->setHeader('Content-Type', 'application/json');
144
                break;
145
            case 'CLI':
146
            case 'HTML':
147
            default:
148
                break;
149
        }
150
151
        $this->type = $type;
152
    }
153
154
    /**
155
     * Gets the HTTP protocol version as a string
156
     *
157
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
158
     *
159
     * @return string HTTP protocol version.
160
     */
161 1
    public function getProtocolVersion()
162
    {
163 1
        return $this->protocol;
164
    }
165
166
    /**
167
     * Gets the response Status-Code
168
     *
169
     * The Status-Code is a 3-digit integer result code of the server's attempt
170
     * to understand and satisfy the request.
171
     *
172
     * @return integer status code.
173
     */
174
    public function getStatusCode()
175
    {
176
        return $this->code;
177
    }
178
179
    /**
180
     * Sets the status code of this response
181
     *
182
     * @param  integer $code the 3-digit integer result code to set.
183
     * @return void
184
     */
185 1
    public function setStatusCode($code)
186
    {
187 1
        $this->code = (int) $code;
188 1
    }
189
190
    /**
191
     * Gets the response Reason-Phrase, a short textual description of the Status-Code
192
     *
193
     * Because a Reason-Phrase is not a required element in response
194
     * Status-Line, the Reason-Phrase value MAY be null. Implementations MAY
195
     * choose to return the default RFC 2616 recommended reason phrase for the
196
     * response's Status-Code.
197
     *
198
     * @return string|null reason phrase, or null if unknown.
199
     */
200 2
    public function getReasonPhrase()
201
    {
202 2
        return $this->phrase;
203
    }
204
205
    /**
206
     * Sets the Reason-Phrase of the response
207
     *
208
     * If no Reason-Phrase is specified, implementations MAY choose to default
209
     * to the RFC 2616 recommended reason phrase for the response's Status-Code.
210
     *
211
     * @param string $phrase the Reason-Phrase to set.
212
     */
213 1
    public function setReasonPhrase($phrase)
214
    {
215 1
        $this->phrase = $phrase;
216 1
    }
217
218
    /**
219
     * Retrieve a header by the given case-insensitive name as a string
220
     *
221
     * This method returns all of the header values of the given
222
     * case-insensitive header name as a string concatenated together using
223
     * a comma.
224
     *
225
     * @param  string $header case-insensitive header name.
226
     * @return string
227
     */
228 2
    public function getHeader($header)
229
    {
230 2
        if ($this->hasHeader($header)) {
231 2
            return join(', ', $this->headers[$header]);
232
        } else {
233 1
            return '';
234
        }
235
    }
236
237
    /**
238
     * Retrieves a header by the given case-insensitive name as an array of strings
239
     *
240
     * @param  string $header Case-insensitive header name.
241
     * @return string[]
242
     */
243 1
    public function getHeaderAsArray($header)
244
    {
245 1
        if ($this->hasHeader($header)) {
246 1
            return $this->headers[$header];
247
        } else {
248 1
            return array();
249
        }
250
    }
251
252
    /**
253
     * Checks if a header exists by the given case-insensitive name
254
     *
255
     * @param  string $header case-insensitive header name.
256
     * @return bool returns true if any header names match the given header
257
     *              name using a case-insensitive string comparison. Returns false if
258
     *              no matching header name is found in the message.
259
     */
260 1
    public function hasHeader($header)
261
    {
262 1
        return isset($this->headers[$header]);
263
    }
264
265
    /**
266
     * Sets a header, replacing any existing values of any headers with the
267
     * same case-insensitive name
268
     *
269
     * The header name is case-insensitive. The header values MUST be a string
270
     * or an array of strings.
271
     *
272
     * @param  string          $header header name
273
     * @param  string|string[] $value  header value(s)
274
     * @return void
275
     */
276 1
    public function setHeader($header, $value)
277
    {
278 1
        $this->headers[$header] = (array) $value;
279 1
    }
280
281
    /**
282
     * Appends a header value for the specified header
283
     *
284
     * Existing values for the specified header will be maintained. The new
285
     * value will be appended to the existing list.
286
     *
287
     * @param  string $header header name to add
288
     * @param  string $value  value of the header
289
     * @return void
290
     */
291 1
    public function addHeader($header, $value)
292
    {
293 1
        if ($this->hasHeader($header)) {
294 1
            $this->headers[$header][] = $value;
295
        } else {
296 1
            $this->setHeader($header, $value);
297
        }
298 1
    }
299
300
    /**
301
     * Remove a specific header by case-insensitive name.
302
     *
303
     * @param  string $header HTTP header to remove
304
     * @return void
305
     */
306 1
    public function removeHeader($header)
307
    {
308 1
        unset($this->headers[$header]);
309 1
    }
310
311
    /**
312
     * Gets all message headers
313
     *
314
     * The keys represent the header name as it will be sent over the wire, and
315
     * each value is an array of strings associated with the header.
316
     *
317
     *     // Represent the headers as a string
318
     *     foreach ($message->getHeaders() as $name => $values) {
319
     *         echo $name . ": " . implode(", ", $values);
320
     *     }
321
     *
322
     * @return array returns an associative array of the message's headers.
323
     */
324 1
    public function getHeaders()
325
    {
326 1
        return $this->headers;
327
    }
328
329
    /**
330
     * Sets headers, replacing any headers that have already been set on the message
331
     *
332
     * The array keys MUST be a string. The array values must be either a
333
     * string or an array of strings.
334
     *
335
     * @param  array $headers Headers to set.
336
     * @return void
337
     */
338 1
    public function setHeaders(array $headers)
339
    {
340 1
        $this->headers = $headers;
341 1
    }
342
343
    /**
344
     * Merges in an associative array of headers.
345
     *
346
     * Each array key MUST be a string representing the case-insensitive name
347
     * of a header. Each value MUST be either a string or an array of strings.
348
     * For each value, the value is appended to any existing header of the same
349
     * name, or, if a header does not already exist by the given name, then the
350
     * header is added.
351
     *
352
     * @param  array $headers Associative array of headers to add to the message
353
     * @return void
354
     */
355 1
    public function addHeaders(array $headers)
356
    {
357 1
        $this->headers = array_merge_recursive($this->headers, $headers);
358 1
    }
359
360
    /**
361
     * Remove all headers
362
     *
363
     * @return void
364
     */
365 2
    public function removeHeaders()
366
    {
367 2
        $this->headers = array();
368 2
    }
369
370
    /**
371
     * Set response body
372
     *
373
     * @param  mixed $body
374
     * @return void
375
     */
376 2
    public function setBody($body)
377
    {
378 2
        $this->body = $body;
379 2
    }
380
381
    /**
382
     * Get response body
383
     *
384
     * @return Controller|Layout
385
     */
386
    public function getBody()
387
    {
388
        return $this->body;
389
    }
390
391
    /**
392
     * Clear response body
393
     *
394
     * @return void
395
     */
396 1
    public function clearBody()
397
    {
398 1
        $this->body = null;
399 1
    }
400
401
    /**
402
     * Set Cookie
403
     *
404
     * @param  string               $name
405
     * @param  string               $value
406
     * @param  int|string|\DateTime $expire
407
     * @param  string               $path
408
     * @param  string               $domain
409
     * @param  bool                 $secure
410
     * @param  bool                 $httpOnly
411
     * @return void
412
     */
413 5
    public function setCookie(
414
        $name,
415
        $value = '',
416
        $expire = 0,
417
        $path = '/',
418
        $domain = '',
419
        $secure = false,
420
        $httpOnly = false
421
    ) {
422
        // from PHP source code
423 5
        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
424 1
            throw new \InvalidArgumentException('The cookie name contains invalid characters.');
425
        }
426
427 4
        if (empty($name)) {
428 1
            throw new \InvalidArgumentException('The cookie name cannot be empty.');
429
        }
430
431
        // convert expiration time to a Unix timestamp
432 3
        if ($expire instanceof \DateTime) {
433 1
            $expire = $expire->format('U');
434 2
        } elseif (!is_numeric($expire)) {
435 1
            $expire = strtotime($expire);
436 1
            if (false === $expire || -1 === $expire) {
437 1
                throw new \InvalidArgumentException('The cookie expiration time is not valid.');
438
            }
439
        }
440
441 2
        $this->cookies[$name] = [
442 2
            'name' => $name,
443 2
            'value' => $value,
444 2
            'expire' => $expire,
445 2
            'path' => $path,
446 2
            'domain' => $domain,
447 2
            'secure' => (bool) $secure,
448 2
            'httpOnly' => (bool) $httpOnly
449
        ];
450 2
    }
451
452
    /**
453
     * Get Cookie by name
454
     *
455
     * @param  string $name
456
     * @return array|null
457
     */
458 2
    public function getCookie($name)
459
    {
460 2
        return isset($this->cookies[$name])?$this->cookies[$name]:null;
461
    }
462
463
    /**
464
     * Process Cookies to Header
465
     *
466
     *   Set-Cookie: <name>=<value>[; <name>=<value>]...
467
     *   [; expires=<date>][; domain=<domain_name>]
468
     *   [; path=<some_path>][; secure][; httponly]
469
     *
470
     * @return void
471
     */
472
    protected function sendCookies()
473
    {
474
        foreach ($this->cookies as $cookie) {
475
            setcookie(...array_values($cookie));
476
        }
477
    }
478
}
479