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