Completed
Push — master ( 6f2bb4...6400d0 )
by Anton
11s
created

Response::setStatusCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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