Completed
Pull Request — master (#42)
by Frederik
02:22
created

ItemList::withBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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