Completed
Pull Request — master (#389)
by Anton
05:06
created

Response::getStatusCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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