Passed
Pull Request — master (#315)
by Théo
02:34
created

Tokens::getToken()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 34
rs 8.8333
c 0
b 0
f 0
cc 7
nc 10
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Annotation;
16
17
use ArrayAccess;
18
use Countable;
19
use Doctrine\Common\Annotations\DocLexer;
20
use Iterator;
21
use KevinGH\Box\Annotation\Exception\Exception;
22
use KevinGH\Box\Annotation\Exception\InvalidTokenException;
23
use KevinGH\Box\Annotation\Exception\LogicException;
24
use KevinGH\Box\Annotation\Exception\OutOfRangeException;
25
26
/**
27
 * Manages and validates an immutable list of tokens.
28
 *
29
 * @author Kevin Herrera <[email protected]>
30
 */
31
class Tokens implements ArrayAccess, Countable, Iterator
32
{
33
    /**
34
     * The list of tokens that are expected to have a value.
35
     *
36
     * @var array
37
     */
38
    private static $hasValue = [
39
        DocLexer::T_FALSE => true,
40
        DocLexer::T_FLOAT => true,
41
        DocLexer::T_IDENTIFIER => true,
42
        DocLexer::T_INTEGER => true,
43
        DocLexer::T_NULL => true,
44
        DocLexer::T_STRING => true,
45
        DocLexer::T_TRUE => true,
46
    ];
47
48
    /**
49
     * The current offset.
50
     *
51
     * @var int
52
     */
53
    private $offset;
54
55
    /**
56
     * The list of tokens.
57
     *
58
     * @var array
59
     */
60
    private $tokens;
61
62
    /**
63
     * The list of valid tokens.
64
     *
65
     * @var array
66
     */
67
    private static $valid = [
68
        DocLexer::T_AT => true,
69
        DocLexer::T_CLOSE_CURLY_BRACES => true,
70
        DocLexer::T_CLOSE_PARENTHESIS => true,
71
        DocLexer::T_COLON => true,
72
        DocLexer::T_COMMA => true,
73
        DocLexer::T_EQUALS => true,
74
        DocLexer::T_FALSE => true,
75
        DocLexer::T_FLOAT => true,
76
        DocLexer::T_IDENTIFIER => true,
77
        DocLexer::T_INTEGER => true,
78
        DocLexer::T_NAMESPACE_SEPARATOR => true,
79
        DocLexer::T_NONE => true,
80
        DocLexer::T_NULL => true,
81
        DocLexer::T_OPEN_CURLY_BRACES => true,
82
        DocLexer::T_OPEN_PARENTHESIS => true,
83
        DocLexer::T_STRING => true,
84
        DocLexer::T_TRUE => true,
85
    ];
86
87
    /**
88
     * Sets the list of tokens to manage.
89
     *
90
     * @param array $tokens the list of tokens
91
     */
92
    public function __construct(array $tokens)
93
    {
94
        $this->offset = 0;
95
        $this->tokens = array_values($tokens);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function count()
102
    {
103
        return count($this->tokens);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function current()
110
    {
111
        return $this->getToken($this->offset);
112
    }
113
114
    /**
115
     * Returns the array copy of the list of tokens.
116
     *
117
     * @return array the list of tokens
118
     */
119
    public function getArray(): array
120
    {
121
        return $this->tokens;
122
    }
123
124
    /**
125
     * Returns the token identifier at the offset.
126
     *
127
     * @param int $offset the offset to retrieve
128
     *
129
     * @return int the token identifier
130
     */
131
    public function getId($offset = null): ?int
132
    {
133
        if (null !== ($token = $this->getToken($offset))) {
134
            return $token[0];
135
        }
136
137
        return null;
138
    }
139
140
    /**
141
     * Returns the key for the value at the offset.
142
     *
143
     * @param int $offset the value's offset
144
     *
145
     * @return int|string the key
146
     */
147
    public function getKey($offset = null)
148
    {
149
        if (null === $offset) {
150
            $offset = $this->offset;
151
        }
152
153
        if ((null !== ($op = $this->getId(--$offset)))
154
            && ((DocLexer::T_COLON === $op)
155
                || (DocLexer::T_EQUALS === $op))) {
156
            return $this->getValue(--$offset);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getValue(--$offset) also could return the type boolean which is incompatible with the documented return type integer|string.
Loading history...
157
        }
158
159
        return null;
160
    }
161
162
    /**
163
     * Returns the token at the offset, or the default given.
164
     *
165
     * @param int   $offset  the offset to retrieve
166
     * @param array $default the default token to return
167
     *
168
     * @throws Exception
169
     * @throws InvalidTokenException if the token is not valid
170
     *
171
     * @return array the token, or the default
172
     */
173
    public function getToken($offset = null, array $default = null): ?array
174
    {
175
        if (null === $offset) {
176
            $offset = $this->offset;
177
        }
178
179
        if (isset($this->tokens[$offset])) {
180
            if (!isset($this->tokens[$offset][0])) {
181
                throw InvalidTokenException::create(
182
                    'Token #%d is missing its token identifier.',
183
                    $offset
184
                );
185
            }
186
187
            if (!isset(self::$valid[$this->tokens[$offset][0]])) {
188
                throw InvalidTokenException::create(
189
                    'Token #%d does not have a valid token identifier.',
190
                    $offset
191
                );
192
            }
193
194
            if ((isset(self::$hasValue[$this->tokens[$offset][0]]))
195
                && !isset($this->tokens[$offset][1])) {
196
                throw InvalidTokenException::create(
197
                    'Token #%d (%d) is missing its value.',
198
                    $offset,
199
                    $this->tokens[$offset][0]
200
                );
201
            }
202
203
            return $this->tokens[$offset];
204
        }
205
206
        return $default;
207
    }
208
209
    /**
210
     * Returns the processed value of the specified token.
211
     *
212
     * @param int $offset the token offset
213
     *
214
     * @throws Exception
215
     * @throws LogicException if the token is not expected to have a value
216
     *
217
     * @return mixed the processed value
218
     */
219
    public function getValue($offset = null)
220
    {
221
        if (null === $offset) {
222
            $offset = $this->offset;
223
        }
224
225
        $token = $this->getToken($offset);
226
227
        if (!isset(self::$hasValue[$token[0]])) {
228
            throw LogicException::create(
229
                'Token #%d (%d) is not expected to have a value.',
230
                $offset,
231
                $token[0]
232
            );
233
        }
234
235
        switch ($token[0]) {
236
            case DocLexer::T_FALSE:
237
                return false;
238
            case DocLexer::T_FLOAT:
239
                return (float) $token[1];
240
            case DocLexer::T_INTEGER:
241
                return (int) $token[1];
242
            case DocLexer::T_IDENTIFIER:
243
            case DocLexer::T_STRING:
244
                return $token[1];
245
            case DocLexer::T_TRUE:
246
                return true;
247
        }
248
249
        return null;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function key()
256
    {
257
        return $this->offset;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function next()
264
    {
265
        ++$this->offset;
266
267
        if (isset($this->tokens[$this->offset])) {
268
            return $this->tokens[$this->offset];
269
        }
270
271
        return null;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function offsetExists($offset)
278
    {
279
        return isset($this->tokens[$offset]);
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     *
285
     * @throws OutOfRangeException if the offset is invalid
286
     */
287
    public function offsetGet($offset)
288
    {
289
        if (null === ($token = $this->getToken($offset))) {
290
            throw OutOfRangeException::create(
291
                'No value is set at offset %d.',
292
                $offset
293
            );
294
        }
295
296
        return $token;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     *
302
     * @throws LogicException if called
303
     */
304
    public function offsetSet($offset, $value): void
305
    {
306
        throw LogicException::create(
307
            'New values cannot be added to the list of tokens.'
308
        );
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     *
314
     * @throws LogicException if called
315
     */
316
    public function offsetUnset($offset): void
317
    {
318
        throw Logicexception::create(
319
            'Existing tokens cannot be removed from the list of tokens.'
320
        );
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326
    public function rewind(): void
327
    {
328
        $this->offset = 0;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334
    public function valid()
335
    {
336
        return isset($this->tokens[$this->offset]);
337
    }
338
}
339