ItemList::fromBytes()   F
last analyzed

Complexity

Conditions 32
Paths 56

Size

Total Lines 160

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 107
CRAP Score 32

Importance

Changes 0
Metric Value
dl 0
loc 160
ccs 107
cts 107
cp 1
rs 3.3333
c 0
b 0
f 0
cc 32
nc 56
nop 1
crap 32

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Protocol\Imap\MessageData;
5
6
use Genkgo\Mail\Protocol\Imap\MessageData\Item\FlagsItem;
7
use Genkgo\Mail\Protocol\Imap\MessageData\Item\NameItem;
8
use Genkgo\Mail\Protocol\Imap\MessageData\Item\PartialItem;
9
use Genkgo\Mail\Protocol\Imap\MessageData\Item\SectionItem;
10
11
final class ItemList
12
{
13
    private const STATE_START = 0;
14
15
    private const STATE_NONE = 1;
16
17
    private const STATE_NAME = 2;
18
19
    private const STATE_SECTION = 3;
20
21
    private const STATE_PARTIAL = 4;
22
23
    private const STATE_OCTET = 5;
24
25
    private const STATE_FLAGS = 6;
26
27
    private const STATE_BODY = 7;
28
29
    /**
30
     * @var array<string, ItemInterface>
31
     */
32
    private $list = [];
33
34
    /**
35
     * @var int
36
     */
37
    private $size;
38
39
    /**
40
     * @var string
41
     */
42
    private $body;
43
44
    /**
45
     * @param array|ItemInterface[] $list
46
     */
47 35
    public function __construct(array $list = [])
48
    {
49 35
        foreach ($list as $item) {
50 2
            $this->list[$item->getName()] = $item;
51
        }
52 35
    }
53
54
    /**
55
     * @param ItemInterface $item
56
     * @return ItemList
57
     */
58 6
    public function withItem(ItemInterface $item): self
59
    {
60 6
        $clone = clone $this;
61 6
        $clone->list[$item->getName()] = $item;
62 6
        return $clone;
63
    }
64
65
    /**
66
     * @param int $size
67
     * @return ItemList
68
     */
69 3
    public function withOctet(int $size): self
70
    {
71 3
        $clone = clone $this;
72 3
        $clone->size = $size;
73 3
        return $clone;
74
    }
75
76
    /**
77
     * @param string $body
78
     * @return ItemList
79
     */
80 2
    public function withBody(string $body): self
81
    {
82 2
        $clone = clone $this;
83 2
        $clone->body = $body;
84 2
        return $clone;
85
    }
86
87
    /**
88
     * @return string
89
     */
90 1
    public function getBody(): string
91
    {
92 1
        return $this->body;
93
    }
94
95
    /**
96
     * @param string $name
97
     * @return ItemInterface
98
     */
99 6
    public function getItem(string $name): ItemInterface
100
    {
101 6
        if (!isset($this->list[$name])) {
102 1
            throw new \UnexpectedValueException(
103 1
                \sprintf('Unknown name %s', $name)
104
            );
105
        }
106
107 5
        return $this->list[$name];
108
    }
109
110
    /**
111
     * @return ItemInterface
112
     */
113 9
    public function last(): ItemInterface
114
    {
115 9
        if (empty($this->list)) {
116 1
            throw new \OutOfBoundsException('Cannot return last item from empty list');
117
        }
118
119 8
        return \end($this->list);
120
    }
121
122
    /**
123
     * @return string
124
     */
125 11
    public function __toString(): string
126
    {
127 11
        $items = \implode(
128 11
            ' ',
129 11
            \array_map(
130
                function (ItemInterface $item) {
131 11
                    return (string)$item;
132 11
                },
133 11
                $this->list
134
            )
135
        );
136
137 11
        if ($this->size) {
138 7
            $items .= ' {' . $this->size . '}';
139
        }
140
141 11
        if ($this->body) {
142 6
            $items .= "\r\n" . $this->body;
143
        }
144
145 11
        if (\count($this->list) > 1 || $this->body) {
146 7
            return '(' . $items . ')';
147
        }
148
149 4
        return '(' . $items . ')';
150
    }
151
152
    /**
153
     * @param string $serializedList
154
     * @return ItemList
155
     */
156 22
    public static function fromString(string $serializedList): self
157
    {
158 22
        if ($serializedList === '') {
159 1
            throw new \InvalidArgumentException('Cannot create list from empty string');
160
        }
161
162 21
        return self::fromBytes(new \ArrayIterator(\str_split($serializedList)));
163
    }
164
165
    /**
166
     * @param \Iterator<int, string> $bytes
167
     * @return ItemList
168
     */
169 26
    public static function fromBytes(\Iterator $bytes): self
170
    {
171 26
        $list = new self();
172
173 26
        $state = self::STATE_START;
174 26
        $sequence = '';
175 26
        $remainingBodyBytes = 0;
176
177 26
        foreach ($bytes as $key => $char) {
178 26
            $sequence .= $char;
179
180 26
            if ($state === self::STATE_START) {
181 26
                if ($sequence !== '(') {
182 1
                    throw new \UnexpectedValueException('Expecting ( as start of item list');
183
                }
184
185 25
                $state = self::STATE_NAME;
186 25
                $sequence = '';
187 25
                continue;
188
            }
189
190 25
            if ($state === self::STATE_BODY) {
191 10
                $remainingBodyBytes--;
192 10
                if ($remainingBodyBytes === 0) {
193 9
                    $list->body = $sequence;
194 9
                    $bytes->next();
195 9
                    if ($bytes->current() !== ')') {
196 1
                        throw new \UnexpectedValueException('List contains extra data after body, expecting )');
197
                    }
198
199 8
                    return $list;
200
                }
201
202 10
                continue;
203
            }
204
205 25
            if ($state !== self::STATE_BODY) {
206 25
                switch ($char) {
207 25
                    case '(':
208 1
                        $sequence = '';
209 1
                        if ($state === self::STATE_NONE) {
210 1
                            $lastKey = \array_key_last($list->list);
211 1
                            $sequence .= $list->list[$lastKey]->getName() . ' ';
212 1
                            unset($list->list[$lastKey]);
213
                        }
214
215 1
                        $sequence .= '(';
216 1
                        $state = self::STATE_FLAGS;
217 1
                        break;
218 25
                    case ')':
219 8
                        if ($state === self::STATE_FLAGS) {
220 1
                            $flagsItem = FlagsItem::fromString($sequence);
221 1
                            $list->list[$flagsItem->getName()] = $flagsItem;
222 1
                            $sequence = '';
223 1
                            $state = self::STATE_NAME;
224 1
                            break;
225
                        }
226
227 8
                        if ($sequence) {
228 8
                            $nameItem = new NameItem(\substr($sequence, 0, -1));
229 8
                            $list->list[$nameItem->getName()] = $nameItem;
230
                        }
231
232 8
                        return $list;
233 25
                    case '[':
234 12
                        if ($state !== self::STATE_NAME && $state !== self::STATE_NONE) {
235 1
                            throw new \InvalidArgumentException('Invalid character [ found');
236
                        }
237
238 12
                        $nameItem = new NameItem(\substr($sequence, 0, -1));
239 12
                        $list->list[$nameItem->getName()] = $nameItem;
240
241 12
                        $sequence = '[';
242 12
                        $state = self::STATE_SECTION;
243 12
                        break;
244 25
                    case ']':
245 8
                        if ($state !== self::STATE_SECTION) {
246 1
                            throw new \InvalidArgumentException('Invalid character ] found');
247
                        }
248
249 7
                        $sectionItem = new SectionItem($list->last(), SectionList::fromString($sequence));
250 7
                        $list->list[$sectionItem->getName()] = $sectionItem;
251
252 7
                        $sequence = '';
253 7
                        $state = self::STATE_NAME;
254 7
                        break;
255 25
                    case '<':
256 3
                        if ($state !== self::STATE_NAME) {
257 1
                            throw new \InvalidArgumentException('Invalid character < found');
258
                        }
259
260 2
                        $state = self::STATE_PARTIAL;
261 2
                        break;
262 25
                    case '>':
263 3
                        if ($state !== self::STATE_PARTIAL) {
264 1
                            throw new \InvalidArgumentException('Invalid character > found');
265
                        }
266
267 2
                        $partialItem = new PartialItem($list->last(), Partial::fromString($sequence));
268 2
                        $list->list[$partialItem->getName()] = $partialItem;
269
270 2
                        $sequence = '';
271 2
                        $state = self::STATE_NAME;
272 2
                        break;
273 25
                    case '{':
274 13
                        if ($state !== self::STATE_NONE) {
275 1
                            throw new \InvalidArgumentException('Invalid character { found');
276
                        }
277
278 12
                        $state = self::STATE_OCTET;
279 12
                        break;
280 25
                    case '}':
281 13
                        if ($state !== self::STATE_OCTET) {
282 1
                            throw new \InvalidArgumentException('Invalid characters } found');
283
                        }
284
285 12
                        $crlf = '';
286 12
                        $bytes->next();
287 12
                        $crlf .= $bytes->current();
288 12
                        $bytes->next();
289 12
                        $crlf .= $bytes->current();
290
291 12
                        if ($crlf !== "\r\n") {
292 2
                            throw new \UnexpectedValueException('Octet is expected to be followed by a CRLF');
293
                        }
294
295 10
                        $list->size = (int)\substr($sequence, 1, -1);
296 10
                        $sequence = '';
297
298 10
                        $remainingBodyBytes = $list->size;
299 10
                        $state = self::STATE_BODY;
300 10
                        break;
301 25
                    case ' ':
302 14
                        if ($sequence === ' ') {
303 6
                            $state = self::STATE_NONE;
304
                        }
305
306 14
                        if ($state === self::STATE_NONE) {
307 6
                            $sequence = '';
308
                        }
309
310 14
                        if ($state === self::STATE_NAME) {
311 9
                            $nameItem = new NameItem(\substr($sequence, 0, -1));
312 9
                            $list->list[$nameItem->getName()] = $nameItem;
313
314 9
                            $sequence = '';
315 9
                            $state = self::STATE_NONE;
316
                        }
317
318 14
                        break;
319
                }
320
            }
321
        }
322
323 2
        if ($remainingBodyBytes > 0) {
324 1
            throw new \UnexpectedValueException('Unexpected end of item list, expecting more bytes');
325
        }
326
327 1
        throw new \UnexpectedValueException('Unexpected end of item list, expecting ) to finish the list');
328
    }
329
}
330