Code

< 40 %
40-60 %
> 60 %
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(
39
        mixed $content = '',
40 51
        int $statusCode = HttpStatus::OK,
41
        array $headers = [])
42 51
    {
43 51
        $this->setStatus($this, $statusCode);
44 51
        $this->setHeaders($headers);
45 51
        $this->stream = create_stream($content);
46
    }
47 33
48
    public function getStatusCode(): int
49 33
    {
50
        return $this->statusCode;
51
    }
52 6
53
    public function withStatus($code, $reasonPhrase = ''): static
54 6
    {
55
        return $this->setStatus(clone $this, (int)$code, (string)$reasonPhrase);
56
    }
57 9
58
    public function getReasonPhrase(): string
59 9
    {
60
        return (string)$this->reasonPhrase;
61
    }
62 2
63
    public function getContentType(): string
64 2
    {
65
        return $this->getHeaderLine('Content-Type') ?: 'text/html';
66
    }
67 4
68
    public function sendHeaders(): void
69 4
    {
70
        $this->prepareResponse();
71 4
        if (false === headers_sent()) {
72 1
            foreach ($this->getHeaders() as $name => $values) {
73
                header($name . ':' . \join(',', (array)$values), false, $this->statusCode);
74
            }
75
            // Status header
76 3
            header(\sprintf('HTTP/%s %d %s',
77 2
                $this->getProtocolVersion(),
78
                $this->getStatusCode(),
79
                $this->getReasonPhrase()),
80
                true,
81 3
                $this->statusCode);
82 3
        }
83
    }
84
85 3
    public function sendBody(): string
86
    {
87
        try {
88 51
            return (string)$this->stream;
89
        } finally {
90 51
            $this->stream->close();
91 1
        }
92 1
    }
93
94
    public function send(): string
95
    {
96 51
        $this->sendHeaders();
97 51
        return $this->sendBody();
98
    }
99 51
100
    protected function setStatus(ServerResponse $instance, int $statusCode, string $reasonPhrase = ''): ServerResponse
101
    {
102 4
        if ($statusCode < 100 || $statusCode > 599) {
103
            throw new \InvalidArgumentException(
104 4
                \sprintf(self::E_INVALID_STATUS_CODE, $statusCode), HttpStatus::UNPROCESSABLE_ENTITY
105 1
            );
106 1
        }
107 1
        $instance->statusCode   = (int)$statusCode;
108
        $instance->reasonPhrase = $reasonPhrase ?: HttpStatus::CODE[$statusCode];
109 1
        return $instance;
110
    }
111
112 3
    protected function prepareResponse(): void
113 3
    {
114
        if (\in_array($this->getStatusCode(), [100, 101, 102, 204, 304])) {
115
            $this->stream = create_stream(null);
116 3
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
117 2
            unset($this->headersMap['content-type'], $this->headers['Content-Type']);
118
            return;
119
        }
120 3
        if ($size = $this->stream->getSize()) {
121 1
            $this->normalizeHeader('Content-Length', (string)$size, true);
122
        }
123 3
        $method = \strtoupper($_SERVER['REQUEST_METHOD'] ?? '');
124
        if (Request::HEAD === $method || Request::OPTIONS === $method) {
125
            $this->stream = create_stream(null);
126
        }
127
        if ($this->hasHeader('Transfer-Encoding') || !$size) {
128
            unset($this->headersMap['content-length'], $this->headers['Content-Length']);
129
        }
130
    }
131
}
132