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