Completed
Pull Request — master (#42)
by Frederik
06:33
created

ItemList::__toString()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.439
c 0
b 0
f 0
cc 5
eloc 14
nc 8
nop 0
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
    public function __construct(array $list = [])
50
    {
51
        $this->list = $list;
52
    }
53
54
    /**
55
     * @param ItemInterface $item
56
     * @return ItemList
57
     */
58
    public function withItem(ItemInterface $item): self
59
    {
60
        $clone = clone $this;
61
        $clone->list[$item->getName()] = $item;
62
        return $clone;
63
    }
64
65
    /**
66
     * @param int $size
67
     * @return ItemList
68
     */
69
    public function withOctet(int $size): self
70
    {
71
        $clone = clone $this;
72
        $clone->size = $size;
73
        return $clone;
74
    }
75
76
    /**
77
     * @param string $body
78
     * @return ItemList
79
     */
80
    public function withBody(string $body): self
81
    {
82
        $clone = clone $this;
83
        $clone->body = $body;
84
        return $clone;
85
    }
86
87
    /**
88
     * @return string
89
     */
90
    public function getBody(): string
91
    {
92
        return $this->body;
93
    }
94
95
    /**
96
     * @param string $name
97
     * @return ItemInterface
98
     */
99
    public function getItem(string $name): ItemInterface
100
    {
101
        if (!isset($this->list[$name])) {
102
            throw new \UnexpectedValueException(
103
                sprintf('Unknown name %s', $name)
104
            );
105
        }
106
107
        return $this->list[$name];
108
    }
109
110
    /**
111
     * @return ItemInterface
112
     */
113
    public function last(): ItemInterface
114
    {
115
        if (empty($this->list)) {
116
            throw new \OutOfBoundsException('Cannot return last item from empty list');
117
        }
118
119
        return end($this->list);
120
    }
121
122
    /**
123
     * @return string
124
     */
125
    public function __toString(): string
126
    {
127
        $items = implode(
128
            ' ',
129
            array_map(
130
                function (ItemInterface $item) {
131
                    return (string)$item;
132
                },
133
                $this->list
134
            )
135
        );
136
137
        if ($this->size) {
138
            $items .= ' {' . $this->size . '}';
139
        }
140
141
        if ($this->body) {
142
            $items .= "\n" . $this->body;
143
        }
144
145
        if (count($this->list) > 1 || $this->body) {
146
            return '(' . $items . ')';
147
        }
148
149
        return $items;
150
    }
151
152
    /**
153
     * @param string $serializedList
154
     * @return ItemList
155
     */
156
    public static function fromString(string $serializedList): self
157
    {
158
        $list = new self();
159
160
        $index = 0;
161
        $state = self::STATE_NAME;
162
        $sequence = '';
163
164
        if ($serializedList === '') {
165
            throw new \InvalidArgumentException('Cannot create list from empty string');
166
        }
167
168
        if ($serializedList[0] === '(' && $serializedList[-1] === ')') {
169
            $serializedList = substr($serializedList, 1, -1);
170
        }
171
172
        while (isset($serializedList[$index])) {
173
            $char = $serializedList[$index];
174
            $sequence .= $char;
175
176
            switch ($char) {
177
                case '[':
178
                    if ($state !== self::STATE_NAME) {
179
                        throw new \InvalidArgumentException('Invalid character [ found');
180
                    }
181
182
                    $list = $list->withItem(new NameItem(substr($sequence, 0, -1)));
183
                    $sequence = '[';
184
                    $state = self::STATE_SECTION;
185
                    break;
186
                case ']':
187
                    if ($state !== self::STATE_SECTION) {
188
                        throw new \InvalidArgumentException('Invalid character ] found');
189
                    }
190
191
                    $list = $list->withItem(
192
                        new SectionItem($list->last(), SectionList::fromString($sequence))
193
                    );
194
195
                    $sequence = '';
196
                    $state = self::STATE_NAME;
197
                    break;
198
                case '<':
199
                    if ($state !== self::STATE_NAME) {
200
                        throw new \InvalidArgumentException('Invalid character < found');
201
                    }
202
203
                    $state = self::STATE_PARTIAL;
204
                    break;
205
                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
                case '{':
218
                    if ($state !== self::STATE_NONE) {
219
                        throw new \InvalidArgumentException('Invalid character { found');
220
                    }
221
222
                    $state = self::STATE_OCTET;
223
                    break;
224
                case '}':
225
                    if ($state !== self::STATE_OCTET) {
226
                        throw new \InvalidArgumentException('Invalid characters } found');
227
                    }
228
229
                    $list = $list->withOctet((int)substr($sequence, 1, -1));
230
                    $sequence = '';
231
232
                    $state = self::STATE_NAME;
233
                    break;
234
                case ' ':
235
                    if ($sequence === ' ') {
236
                        $state = self::STATE_NONE;
237
                    }
238
239
                    if ($state === self::STATE_NONE) {
240
                        $sequence = '';
241
                    }
242
243
                    if ($state === self::STATE_NAME) {
244
                        $list = $list->withItem(new NameItem(substr($sequence, 0, -1)));
245
                        $sequence = '';
246
                        $state = self::STATE_NONE;
247
                    }
248
249
                    break;
250
                case "\n":
251
                    $list = $list->withBody(substr($serializedList, $index + 1));
252
                    $sequence = '';
253
                    break 2;
254
            }
255
256
            $index++;
257
        }
258
259
        if ($sequence) {
260
            $list = $list->withItem(new NameItem($sequence));
261
        }
262
263
        return $list;
264
    }
265
}