Completed
Pull Request — master (#11)
by Alexander
02:53
created

Record   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 0
dl 0
loc 215
c 0
b 0
f 0
ccs 56
cts 56
cp 1
rs 10
1
<?php
2
/*
3
 * Protocol FCGI library
4
 *
5
 * @copyright Copyright 2021. Lisachenko Alexander <[email protected]>
6
 * This source file is subject to the license that is bundled
7
 * with this source code in the file LICENSE.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Lisachenko\Protocol\FCGI;
13
14
use Lisachenko\Protocol\FCGI;
15
use ReflectionClass;
16
17
/**
18
 * FCGI record.
19
 *
20
 * @author Alexander.Lisachenko
21
 */
22
class Record
23
{
24
    /**
25
     * Identifies the FastCGI protocol version.
26
     */
27
    protected int $version = FCGI::VERSION_1;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
28
29
    /**
30
     * Identifies the FastCGI record type, i.e. the general function that the record performs.
31
     */
32
    protected int $type = FCGI::UNKNOWN_TYPE;
33
34
    /**
35
     * Identifies the FastCGI request to which the record belongs.
36
     */
37
    protected int $requestId = FCGI::NULL_REQUEST_ID;
38
39
    /**
40
     * Reserved byte for future proposes
41
     */
42
    protected int $reserved = 0;
43
44
    /**
45
     * The number of bytes in the contentData component of the record.
46
     */
47
    private int $contentLength = 0;
48
49
    /**
50
     * The number of bytes in the paddingData component of the record.
51
     */
52
    private int $paddingLength = 0;
53
54
    /**
55
     * Binary data, between 0 and 65535 bytes of data, interpreted according to the record type.
56
     */
57
    private string $contentData = '';
58
59
    /**
60
     * Padding data, between 0 and 255 bytes of data, which are ignored.
61
     */
62
    private string $paddingData = '';
63
64
    /**
65
     * Unpacks the message from the binary data buffer
66
     *
67
     * @return static
68
     */
69
    final public static function unpack(string $binaryData): self
70
    {
71
        /** @var static $self */
72
        $self   = (new ReflectionClass(static::class))->newInstanceWithoutConstructor();
73
74
        /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */
75
        $packet = unpack(FCGI::HEADER_FORMAT, $binaryData);
76
        if ($packet === false) {
77
            throw new \RuntimeException('Can not unpack data from the binary buffer');
78
        }
79 14
        [
80
            $self->version,
81 14
            $self->type,
82
            $self->requestId,
83 14
            $self->contentLength,
84 14
            $self->paddingLength,
85 14
            $self->reserved
86 14
        ] = array_values($packet);
87 14
88 14
        $payload = substr($binaryData, FCGI::HEADER_LEN);
89 14
        self::unpackPayload($self, $payload);
90
        if (static::class !== self::class && $self->contentLength > 0) {
91 14
            static::unpackPayload($self, $payload);
92 14
        }
93 14
94 11
        return $self;
95 11
    }
96
97 14
    /**
98
     * Returns the binary message representation of record
99
     */
100
    final public function __toString(): string
101
    {
102
        $headerPacket = pack(
103
            "CCnnCC",
104
            $this->version,
105 13
            $this->type,
106
            $this->requestId,
107 13
            $this->contentLength,
108 13
            $this->paddingLength,
109 13
            $this->reserved
110 13
        );
111 13
112 13
        $payloadPacket = $this->packPayload();
113 13
        $paddingPacket = pack("a{$this->paddingLength}", $this->paddingData);
114 13
115 13
        return $headerPacket . $payloadPacket . $paddingPacket;
116
    }
117 13
118 13
    /**
119
     * Sets the content data and adjusts the length fields
120 13
     */
121
    public function setContentData(string $data): self
122
    {
123
        $this->contentData   = $data;
124
        $this->contentLength = strlen($this->contentData);
125
        $extraLength         = $this->contentLength % 8;
126
        $this->paddingLength = $extraLength ? (8 - $extraLength) : 0;
127
128 23
        return $this;
129
    }
130 23
131 23
    /**
132 23
     * Returns the context data from the record
133 23
     */
134 23
    public function getContentData(): string
135
    {
136
        return $this->contentData;
137
    }
138
139
    /**
140
     * Returns the version of record
141 10
     */
142
    public function getVersion(): int
143 10
    {
144
        return $this->version;
145
    }
146
147
    /**
148
     * Returns record type
149
     */
150
    public function getType(): int
151 1
    {
152
        return $this->type;
153 1
    }
154
155
    /**
156
     * Returns request ID
157
     */
158
    public function getRequestId(): int
159 24
    {
160
        return $this->requestId;
161 24
    }
162
163
    /**
164
     * Sets request ID
165
     *
166
     * There should be only one unique ID for all active requests,
167 4
     * use random number or preferably resetting auto-increment.
168
     */
169 4
    public function setRequestId(int $requestId): self
170
    {
171
        $this->requestId = $requestId;
172
173
        return $this;
174
    }
175
176
    /**
177 3
     * Returns the size of content length
178
     */
179 3
    final public function getContentLength(): int
180
    {
181 3
        return $this->contentLength;
182
    }
183
184
    /**
185
     * Returns the size of padding length
186
     */
187
    final public function getPaddingLength(): int
188
    {
189 5
        return $this->paddingLength;
190
    }
191 5
192
    /**
193
     * Method to unpack the payload for the record.
194
     *
195
     * NB: Default implementation will be always called
196
     * @param static $self
197
     */
198
    protected static function unpackPayload(Record $self, string $binaryData): void
199 3
    {
200
        /** @phpstan-var false|array{contentData: string, paddingData: string} */
201 3
        $payload = unpack("a{$self->contentLength}contentData/a{$self->paddingLength}paddingData", $binaryData);
202
        if ($payload === false) {
203
            throw new \RuntimeException('Can not unpack data from the binary buffer');
204
        }
205
        [
206
            $self->contentData,
207
            $self->paddingData
208
        ] = array_values($payload);
209
    }
210
211
    /**
212 14
     * Implementation of packing the payload
213
     */
214
    protected function packPayload(): string
215 14
    {
216 14
        return pack("a{$this->contentLength}", $this->contentData);
217 14
    }
218
}
219