Completed
Push — master ( 0ea9e7...aef2ed )
by Anton
14s
created

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