Completed
Pull Request — master (#470)
by Anton
14:03 queued 11:44
created

Response::setBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
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;
0 ignored issues
show
Bug introduced by
The type Bluz\Layout\Layout was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

498
            setcookie(/** @scrutinizer ignore-type */ ...array_values($cookie));
Loading history...
499
        }
500 1
    }
501
}
502