Completed
Pull Request — master (#357)
by Anton
03:23
created

Response::getHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
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\Common\Options;
15
use Bluz\Proxy\Messages;
16
use Bluz\View\View;
17
use Zend\Diactoros\Response\EmptyResponse;
18
use Zend\Diactoros\Response\HtmlResponse;
19
use Zend\Diactoros\Response\JsonResponse;
20
use Zend\Diactoros\Response\RedirectResponse;
21
use Zend\Diactoros\Response\SapiEmitter;
22
23
/**
24
 * Response Container
25
 *
26
 * @package  Bluz\Response
27
 * @author   Anton Shevchuk
28
 * @link     https://github.com/bluzphp/framework/wiki/Response
29
 */
30
class Response
31
{
32
    use Options;
33
34
    /**
35
     * @var string HTTP protocol version
36
     */
37
    protected $protocol = '1.1';
38
39
    /**
40
     * @var integer response code equal to HTTP status codes
41
     */
42
    protected $code = 200;
43
44
    /**
45
     * @var string|null HTTP Phrase
46
     */
47
    protected $phrase;
48
49
    /**
50
     * @var array list of headers
51
     */
52
    protected $headers = array();
53
54
    /**
55
     * @var array list of cookies
56
     */
57
    protected $cookies = array();
58
59
    /**
60
     * @var mixed result can be View|object|function
61
     */
62
    protected $body;
63
64
    /**
65
     * send
66
     *
67
     * @return void
68
     */
69
    public function send()
70
    {
71
        // switch statement for $this->status
72
        switch ($this->getStatusCode()) {
73
            case 204:
74
                $response = new EmptyResponse($this->getStatusCode(), $this->getHeaders());
75
                break;
76
            case 301:
77
            case 302:
78
                $response = new RedirectResponse(
79
                    $this->getHeader('Location'),
80
                    $this->getStatusCode(),
81
                    $this->getHeaders()
82
                );
83
                break;
84
            default:
85
                $body = $this->getBody();
86
87
                // run callable structure, but don't run view
88
                if (is_callable($body) && !($body instanceof View)) {
89
                    $body = $body();
90
                }
91
92
                if (is_null($body)) {
93
                    // empty response
94
                    $response = new EmptyResponse(
95
                        $this->getStatusCode(),
96
                        $this->getHeaders()
97
                    );
98
                } elseif (PHP_SAPI === 'cli') {
99
                    // CLI response
100
                    // extract data from view
101
                    if ($body instanceof View) {
102
                        // just print to console as key-value pair
103
                        $data = $body->toArray();
104
                        $output = array();
105
                        array_walk_recursive($data, function ($value, $key) use (&$output) {
106
                            $output[] = $key .': '. $value;
107
                        });
108
                        $body = join("\n", $output);
109
                    }
110
111
                    // @TODO: create CLIResponse
112
                    $response = new HtmlResponse(
113
                        (string) $body,
114
                        $this->getStatusCode(),
115
                        $this->getHeaders()
116
                    );
117
                } elseif ($this->getHeader('Content-Type') == 'application/json') {
118
                    // JSON response
119
120
                    // setup messages
121
                    if (Messages::count()) {
122
                        $this->setHeader('Bluz-Notify', json_encode(Messages::popAll()));
123
                    }
124
125
                    // extract data from view
126
                    if ($body instanceof View) {
127
                        $body = $body->toArray();
128
                    }
129
130
                    // encode body data to JSON
131
                    $response = new JsonResponse(
132
                        (array) $body,
133
                        $this->getStatusCode(),
134
                        $this->getHeaders()
135
                    );
136
                } else {
137
                    // HTML response
138
                    $response = new HtmlResponse(
139
                        (string) $body,
140
                        $this->getStatusCode(),
141
                        $this->getHeaders()
142
                    );
143
                }
144
                break;
145
        }
146
147
        $emitter = new SapiEmitter();
148
        $emitter->emit($response);
149
    }
150
151
    /**
152
     * Gets the HTTP protocol version as a string
153
     *
154
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
155
     *
156
     * @return string HTTP protocol version.
157
     */
158 1
    public function getProtocolVersion()
159
    {
160 1
        return $this->protocol;
161
    }
162
163
    /**
164
     * Gets the response Status-Code
165
     *
166
     * The Status-Code is a 3-digit integer result code of the server's attempt
167
     * to understand and satisfy the request.
168
     *
169
     * @return integer status code.
170
     */
171 1
    public function getStatusCode()
172
    {
173 1
        return $this->code;
174
    }
175
176
    /**
177
     * Sets the status code of this response
178
     *
179
     * @param  integer $code the 3-digit integer result code to set.
180
     * @return void
181
     */
182 11
    public function setStatusCode($code)
183
    {
184 11
        $this->code = (int) $code;
185 11
    }
186
187
    /**
188
     * Gets the response Reason-Phrase, a short textual description of the Status-Code
189
     *
190
     * Because a Reason-Phrase is not a required element in response
191
     * Status-Line, the Reason-Phrase value MAY be null. Implementations MAY
192
     * choose to return the default RFC 2616 recommended reason phrase for the
193
     * response's Status-Code.
194
     *
195
     * @return string|null reason phrase, or null if unknown.
196
     */
197 2
    public function getReasonPhrase()
198
    {
199 2
        return $this->phrase;
200
    }
201
202
    /**
203
     * Sets the Reason-Phrase of the response
204
     *
205
     * If no Reason-Phrase is specified, implementations MAY choose to default
206
     * to the RFC 2616 recommended reason phrase for the response's Status-Code.
207
     *
208
     * @param string $phrase the Reason-Phrase to set.
209
     */
210 1
    public function setReasonPhrase($phrase)
211
    {
212 1
        $this->phrase = $phrase;
213 1
    }
214
215
    /**
216
     * Retrieve a header by the given case-insensitive name as a string
217
     *
218
     * This method returns all of the header values of the given
219
     * case-insensitive header name as a string concatenated together using
220
     * a comma.
221
     *
222
     * @param  string $header case-insensitive header name.
223
     * @return string
224
     */
225 4
    public function getHeader($header)
226
    {
227 4
        if ($this->hasHeader($header)) {
228 4
            return join(', ', $this->headers[$header]);
229
        } else {
230 1
            return '';
231
        }
232
    }
233
234
    /**
235
     * Retrieves a header by the given case-insensitive name as an array of strings
236
     *
237
     * @param  string $header Case-insensitive header name.
238
     * @return string[]
239
     */
240 1
    public function getHeaderAsArray($header)
241
    {
242 1
        if ($this->hasHeader($header)) {
243 1
            return $this->headers[$header];
244
        } else {
245 1
            return array();
246
        }
247
    }
248
249
    /**
250
     * Checks if a header exists by the given case-insensitive name
251
     *
252
     * @param  string $header case-insensitive header name.
253
     * @return bool returns true if any header names match the given header
254
     *              name using a case-insensitive string comparison. Returns false if
255
     *              no matching header name is found in the message.
256
     */
257 3
    public function hasHeader($header)
258
    {
259 3
        return isset($this->headers[$header]);
260
    }
261
262
    /**
263
     * Sets a header, replacing any existing values of any headers with the
264
     * same case-insensitive name
265
     *
266
     * The header name is case-insensitive. The header values MUST be a string
267
     * or an array of strings.
268
     *
269
     * @param  string          $header header name
270
     * @param  string|string[] $value  header value(s)
271
     * @return void
272
     */
273 8
    public function setHeader($header, $value)
274
    {
275 8
        $this->headers[$header] = (array) $value;
276 8
    }
277
278
    /**
279
     * Appends a header value for the specified header
280
     *
281
     * Existing values for the specified header will be maintained. The new
282
     * value will be appended to the existing list.
283
     *
284
     * @param  string $header header name to add
285
     * @param  string $value  value of the header
286
     * @return void
287
     */
288 1
    public function addHeader($header, $value)
289
    {
290 1
        if ($this->hasHeader($header)) {
291 1
            $this->headers[$header][] = $value;
292 1
        } else {
293 1
            $this->setHeader($header, $value);
294
        }
295 1
    }
296
297
    /**
298
     * Remove a specific header by case-insensitive name.
299
     *
300
     * @param  string $header HTTP header to remove
301
     * @return void
302
     */
303 1
    public function removeHeader($header)
304
    {
305 1
        unset($this->headers[$header]);
306 1
    }
307
308
    /**
309
     * Gets all message headers
310
     *
311
     * The keys represent the header name as it will be sent over the wire, and
312
     * each value is an array of strings associated with the header.
313
     *
314
     *     // Represent the headers as a string
315
     *     foreach ($message->getHeaders() as $name => $values) {
316
     *         echo $name . ": " . implode(", ", $values);
317
     *     }
318
     *
319
     * @return array returns an associative array of the message's headers.
320
     */
321 1
    public function getHeaders()
322
    {
323 1
        return $this->headers;
324
    }
325
326
    /**
327
     * Sets headers, replacing any headers that have already been set on the message
328
     *
329
     * The array keys MUST be a string. The array values must be either a
330
     * string or an array of strings.
331
     *
332
     * @param  array $headers Headers to set.
333
     * @return void
334
     */
335 1
    public function setHeaders(array $headers)
336
    {
337 1
        $this->headers = $headers;
338 1
    }
339
340
    /**
341
     * Merges in an associative array of headers.
342
     *
343
     * Each array key MUST be a string representing the case-insensitive name
344
     * of a header. Each value MUST be either a string or an array of strings.
345
     * For each value, the value is appended to any existing header of the same
346
     * name, or, if a header does not already exist by the given name, then the
347
     * header is added.
348
     *
349
     * @param  array $headers Associative array of headers to add to the message
350
     * @return void
351
     */
352 1
    public function addHeaders(array $headers)
353
    {
354 1
        $this->headers = array_merge_recursive($this->headers, $headers);
355 1
    }
356
357
    /**
358
     * Remove all headers
359
     *
360
     * @return void
361
     */
362 2
    public function removeHeaders()
363
    {
364 2
        $this->headers = array();
365 2
    }
366
367
    /**
368
     * Set response body
369
     *
370
     * @param  mixed $body
371
     * @return void
372
     */
373 2
    public function setBody($body)
374
    {
375 2
        $this->body = $body;
376 2
    }
377
378
    /**
379
     * Get response body
380
     *
381
     * @return View
382
     */
383
    public function getBody()
384
    {
385
        return $this->body;
386
    }
387
388
    /**
389
     * Clear response body
390
     *
391
     * @return void
392
     */
393 1
    public function clearBody()
394
    {
395 1
        $this->body = null;
396 1
    }
397
398
    /**
399
     * Set Cookie
400
     *
401
     * @param  string               $name
402
     * @param  string               $value
403
     * @param  int|string|\DateTime $expire
404
     * @param  string               $path
405
     * @param  string               $domain
406
     * @param  bool                 $secure
407
     * @param  bool                 $httpOnly
408
     * @return void
409
     */
410 5
    public function setCookie(
411
        $name,
412
        $value = null,
413
        $expire = 0,
414
        $path = '/',
415
        $domain = null,
416
        $secure = false,
417
        $httpOnly = true
418
    ) {
419
        // from PHP source code
420 5
        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
421 1
            throw new \InvalidArgumentException('The cookie name contains invalid characters.');
422
        }
423
424 4
        if (empty($name)) {
425 1
            throw new \InvalidArgumentException('The cookie name cannot be empty.');
426
        }
427
428
        // convert expiration time to a Unix timestamp
429 3
        if ($expire instanceof \DateTime) {
430 1
            $expire = $expire->format('U');
431 3
        } elseif (!is_numeric($expire)) {
432 1
            $expire = strtotime($expire);
433 1
            if (false === $expire || -1 === $expire) {
434 1
                throw new \InvalidArgumentException('The cookie expiration time is not valid.');
435
            }
436
        }
437
438 2
        $this->cookies[$name] = [
439 2
            'name' => $name,
440 2
            'value' => $value,
441 2
            'expire' => $expire,
442 2
            'path' => empty($path) ? '/' : $path,
443 2
            'domain' => $domain,
444 2
            'secure' => (bool) $secure,
445
            'httpOnly' => (bool) $httpOnly
446 2
        ];
447 2
    }
448
449
    /**
450
     * Get Cookie by name
451
     *
452
     * @param  string $name
453
     * @return array|null
454
     */
455 2
    public function getCookie($name)
456
    {
457 2
        return isset($this->cookies[$name])?$this->cookies[$name]:null;
458
    }
459
}
460