Completed
Push — master ( 034ad5...b050ac )
by Frederik
01:44
created

ItemList::__toString()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 14
cts 14
cp 1
rs 9.1928
c 0
b 0
f 0
cc 5
nc 8
nop 0
crap 5
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Protocol\Imap\MessageData;
5
6
use Genkgo\Mail\Protocol\Imap\FlagParenthesizedList;
7
use Genkgo\Mail\Protocol\Imap\MessageData\Item\FlagsItem;
8
use Genkgo\Mail\Protocol\Imap\MessageData\Item\NameItem;
9
use Genkgo\Mail\Protocol\Imap\MessageData\Item\PartialItem;
10
use Genkgo\Mail\Protocol\Imap\MessageData\Item\SectionItem;
11
12
final class ItemList
13
{
14
    private const STATE_NONE = 0;
15
16
    private const STATE_NAME = 1;
17
18
    private const STATE_SECTION = 2;
19
20
    private const STATE_PARTIAL = 3;
21
22
    private const STATE_OCTET = 4;
23
24
    private const STATE_FLAGS = 5;
25
26
    /**
27
     * @var array<string, ItemInterface>
28
     */
29
    private $list = [];
30
31
    /**
32
     * @var int
33
     */
34
    private $size;
35
36
    /**
37
     * @var string
38
     */
39
    private $body;
40
41
    /**
42
     * @param array|ItemInterface[] $list
43
     */
44 23
    public function __construct(array $list = [])
45
    {
46 23
        foreach ($list as $item) {
47 2
            $this->list[$item->getName()] = $item;
48
        }
49 23
    }
50
51
    /**
52
     * @param ItemInterface $item
53
     * @return ItemList
54
     */
55 18
    public function withItem(ItemInterface $item): self
56
    {
57 18
        $clone = clone $this;
58 18
        $clone->list[$item->getName()] = $item;
59 18
        return $clone;
60
    }
61
62
    /**
63
     * @param int $size
64
     * @return ItemList
65
     */
66 6
    public function withOctet(int $size): self
67
    {
68 6
        $clone = clone $this;
69 6
        $clone->size = $size;
70 6
        return $clone;
71
    }
72
73
    /**
74
     * @param string $body
75
     * @return ItemList
76
     */
77 5
    public function withBody(string $body): self
78
    {
79 5
        $clone = clone $this;
80 5
        $clone->body = $body;
81 5
        return $clone;
82
    }
83
84
    /**
85
     * @return string
86
     */
87 1
    public function getBody(): string
88
    {
89 1
        return $this->body;
90
    }
91
92
    /**
93
     * @param string $name
94
     * @return ItemInterface
95
     */
96 6
    public function getItem(string $name): ItemInterface
97
    {
98 6
        if (!isset($this->list[$name])) {
99 1
            throw new \UnexpectedValueException(
100 1
                \sprintf('Unknown name %s', $name)
101
            );
102
        }
103
104 5
        return $this->list[$name];
105
    }
106
107
    /**
108
     * @return ItemInterface
109
     */
110 7
    public function last(): ItemInterface
111
    {
112 7
        if (empty($this->list)) {
113 1
            throw new \OutOfBoundsException('Cannot return last item from empty list');
114
        }
115
116 6
        return \end($this->list);
117
    }
118
119
    /**
120
     * @return string
121
     */
122 6
    public function __toString(): string
123
    {
124 6
        $items = \implode(
125 6
            ' ',
126 6
            \array_map(
127
                function (ItemInterface $item) {
128 6
                    return (string)$item;
129 6
                },
130 6
                $this->list
131
            )
132
        );
133
134 6
        if ($this->size) {
135 3
            $items .= ' {' . $this->size . '}';
136
        }
137
138 6
        if ($this->body) {
139 2
            $items .= "\n" . $this->body;
140
        }
141
142 6
        if (\count($this->list) > 1 || $this->body) {
143 3
            return '(' . $items . ')';
144
        }
145
146 3
        return $items;
147
    }
148
149
    /**
150
     * @param string $serializedList
151
     * @return ItemList
152
     */
153 14
    public static function fromString(string $serializedList): self
154
    {
155 14
        $list = new self();
156
157 14
        $index = 0;
158 14
        $state = self::STATE_NAME;
159 14
        $sequence = '';
160
161 14
        if ($serializedList === '') {
162 1
            throw new \InvalidArgumentException('Cannot create list from empty string');
163
        }
164
165 13
        if ($serializedList[0] === '(' && $serializedList[-1] === ')') {
166 3
            $serializedList = \substr($serializedList, 1, -1);
167
        }
168
169 13
        while (isset($serializedList[$index])) {
170 13
            $char = $serializedList[$index];
171 13
            $sequence .= $char;
172
173 13
            switch ($char) {
174 13
                case '(':
175 1
                    $sequence = '';
176 1
                    if ($state === self::STATE_NONE) {
177 1
                        $lastKey = \array_key_last($list->list);
178 1
                        $sequence .= $list->list[$lastKey]->getName() . ' ';
179 1
                        unset($list->list[$lastKey]);
180
                    }
181
182 1
                    $sequence .= '(';
183 1
                    $state = self::STATE_FLAGS;
184 1
                    break;
185 13
                case ')':
186 1
                    if ($state !== self::STATE_FLAGS) {
187
                        throw new \InvalidArgumentException('Invalid character ) found');
188
                    }
189
190 1
                    $list = $list->withItem(FlagsItem::fromString($sequence));
191 1
                    $sequence = '';
192 1
                    $state = self::STATE_NAME;
193 1
                    break;
194 13
                case '[':
195 10
                    if ($state !== self::STATE_NAME && $state !== self::STATE_NONE) {
196 1
                        throw new \InvalidArgumentException('Invalid character [ found');
197
                    }
198
199 10
                    $list = $list->withItem(new NameItem(\substr($sequence, 0, -1)));
200 10
                    $sequence = '[';
201 10
                    $state = self::STATE_SECTION;
202 10
                    break;
203 13
                case ']':
204 6
                    if ($state !== self::STATE_SECTION) {
205 1
                        throw new \InvalidArgumentException('Invalid character ] found');
206
                    }
207
208 5
                    $list = $list->withItem(
209 5
                        new SectionItem($list->last(), SectionList::fromString($sequence))
210
                    );
211
212 5
                    $sequence = '';
213 5
                    $state = self::STATE_NAME;
214 5
                    break;
215 13
                case '<':
216 3
                    if ($state !== self::STATE_NAME) {
217 1
                        throw new \InvalidArgumentException('Invalid character < found');
218
                    }
219
220 2
                    $state = self::STATE_PARTIAL;
221 2
                    break;
222 13
                case '>':
223 3
                    if ($state !== self::STATE_PARTIAL) {
224 1
                        throw new \InvalidArgumentException('Invalid character > found');
225
                    }
226
227 2
                    $list = $list->withItem(
228 2
                        new PartialItem($list->last(), Partial::fromString($sequence))
229
                    );
230
231 2
                    $sequence = '';
232 2
                    $state = self::STATE_NAME;
233 2
                    break;
234 13
                case '{':
235 4
                    if ($state !== self::STATE_NONE) {
236 1
                        throw new \InvalidArgumentException('Invalid character { found');
237
                    }
238
239 3
                    $state = self::STATE_OCTET;
240 3
                    break;
241 13
                case '}':
242 4
                    if ($state !== self::STATE_OCTET) {
243 1
                        throw new \InvalidArgumentException('Invalid characters } found');
244
                    }
245
246 3
                    $list = $list->withOctet((int)\substr($sequence, 1, -1));
247 3
                    $sequence = '';
248
249 3
                    $state = self::STATE_NAME;
250 3
                    break;
251 13
                case ' ':
252 5
                    if ($sequence === ' ') {
253 4
                        $state = self::STATE_NONE;
254
                    }
255
256 5
                    if ($state === self::STATE_NONE) {
257 4
                        $sequence = '';
258
                    }
259
260 5
                    if ($state === self::STATE_NAME) {
261 2
                        $list = $list->withItem(new NameItem(\substr($sequence, 0, -1)));
262 2
                        $sequence = '';
263 2
                        $state = self::STATE_NONE;
264
                    }
265
266 5
                    break;
267 13
                case "\n":
268 3
                    $list = $list->withBody(\substr($serializedList, $index + 1));
269 3
                    $sequence = '';
270 3
                    break 2;
271
            }
272
273 13
            $index++;
274
        }
275
276 7
        if ($sequence) {
277 2
            $list = $list->withItem(new NameItem($sequence));
278
        }
279
280 7
        return $list;
281
    }
282
}
283