Completed
Push — master ( d2aa89...bbfa5f )
by Mihail
34:12 queued 24:23
created

ServerResponse::sendHeaders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 3
eloc 10
c 3
b 0
f 1
nc 3
nop 0
dl 0
loc 14
ccs 6
cts 6
cp 1
crap 3
rs 9.9332
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\{HttpStatus, Request, Response};
16
17
/**
18
 * Class ServerResponse
19
 *
20
 */
21
class ServerResponse implements Response, \JsonSerializable
22
{
23
    use HeaderTrait, MessageTrait, CookieTrait, JsonSerializeTrait;
24
25
    private const E_CLIENT_RESPONSE_SEND = 'Cannot send the client response.';
26
    private const E_INVALID_STATUS_CODE  = 'Invalid status code %s, expected range between [100-599]';
27
28
    protected int    $statusCode   = HttpStatus::OK;
29
    protected string $reasonPhrase = 'OK';
30
31
    /**
32
     * ServerResponse constructor.
33
     *
34
     * @param mixed $content    [optional]
35
     * @param int   $statusCode [optional]
36
     * @param array $headers    [optional]
37
     */
38
    public function __construct(mixed $content = '', int $statusCode = HttpStatus::OK, array $headers = [])
39
    {
40 51
        $this->setStatus($this, $statusCode);
41
        $this->setHeaders($headers);
42 51
        $this->stream = create_stream($content);
43 51
    }
44 51
45 51
    public function getStatusCode(): int
46
    {
47 33
        return $this->statusCode;
48
    }
49 33
50
    public function withStatus($code, $reasonPhrase = ''): static
51
    {
52 6
        return $this->setStatus(clone $this, (int)$code, (string)$reasonPhrase);
53
    }
54 6
55
    public function getReasonPhrase(): string
56
    {
57 9
        return (string)$this->reasonPhrase;
58
    }
59 9
60
    public function getContentType(): string
61
    {
62 2
        return $this->getHeaderLine('Content-Type') ?: 'text/html';
63
    }
64 2
65
    public function sendHeaders(): void
66
    {
67 4
        $this->prepareResponse();
68
        if (false === headers_sent()) {
69 4
            foreach ($this->getHeaders() as $name => $values) {
70
                header($name . ':' . \join(',', (array)$values), false, $this->statusCode);
71 4
            }
72 1
            // Status header
73
            header(\sprintf('HTTP/%s %d %s',
74
                $this->getProtocolVersion(),
75
                $this->getStatusCode(),
76 3
                $this->getReasonPhrase()),
77 2
                true,
78
                $this->statusCode);
79
        }
80
    }
81 3
82 3
    public function sendBody(): string
83
    {
84
        try {
85 3
            return (string)$this->stream;
86
        } finally {
87
            $this->stream->close();
88 51
        }
89
    }
90 51
91 1
    public function send(): string
92 1
    {
93
        $this->sendHeaders();
94
        return $this->sendBody();
95
    }
96 51
97 51
    protected function setStatus(ServerResponse $instance, int $statusCode, string $reasonPhrase = ''): ServerResponse
98
    {
99 51
        if ($statusCode < 100 || $statusCode > 599) {
100
            throw new \InvalidArgumentException(
101
                \sprintf(self::E_INVALID_STATUS_CODE, $statusCode), HttpStatus::UNPROCESSABLE_ENTITY
102 4
            );
103
        }
104 4
        $instance->statusCode   = (int)$statusCode;
105 1
        $instance->reasonPhrase = $reasonPhrase ? (string)$reasonPhrase : StatusCode::CODE[$statusCode];
106 1
        return $instance;
107 1
    }
108
109 1
    protected function prepareResponse(): void
110
    {
111
        if (\in_array($this->getStatusCode(), [100, 101, 102, 204, 304])) {
112 3
            $this->stream = create_stream(null);
113 3
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
114
            unset($this->headersMap['content-type'], $this->headers['Content-Type']);
115
            return;
116 3
        }
117 2
        if ($size = $this->stream->getSize()) {
118
            $this->normalizeHeader('Content-Length', (string)$size, true);
119
        }
120 3
        $method = \strtoupper($_SERVER['REQUEST_METHOD'] ?? '');
121 1
        if (Request::HEAD === $method || Request::OPTIONS === $method) {
122
            $this->stream = create_stream(null);
123 3
        }
124
        if ($this->hasHeader('Transfer-Encoding') || !$size) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $size of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
125
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
126
        }
127
    }
128
}
129