Completed
Push — master ( 64812a...ac0b2b )
by Nate
03:03
created

PhpType::getOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
namespace Tebru\Gson;
8
9
use stdClass;
10
use Tebru\Gson\Exception\MalformedTypeException;
11
use Tebru\Gson\Internal\TypeToken;
12
13
/**
14
 * Class PhpType
15
 *
16
 * Wrapper around core php types and custom types.  It can be as simply as
17
 *
18
 *     new PhpType('string');
19
 *
20
 * To create a string type.
21
 *
22
 * This class also allows us to fake generic types.  The syntax to
23
 * represent generics uses angle brackets <>.
24
 *
25
 * For example:
26
 *
27
 *     array<int>
28
 *
29
 * Would represent an array of ints.
30
 *
31
 *     array<string, int>
32
 *
33
 * Would represent an array using string keys and int values.
34
 *
35
 * They can be combined, like so
36
 *
37
 *     array<string, array<int>>
38
 *
39
 * To represent a array with string keys and an array of ints as values.
40
 *
41
 * @author Nate Brunette <[email protected]>
42
 */
43
final class PhpType
44
{
45
    /**
46
     * The initial type
47
     *
48
     * @var string
49
     */
50
    private $fullType;
51
52
    /**
53
     * An enum representing core php types
54
     *
55
     * @var string
56
     */
57
    private $type;
58
59
    /**
60
     * If the type is an object, this will be the object's class name
61
     *
62
     * @var string
63
     */
64
    private $class;
65
66
    /**
67
     * Generic types, if they exist
68
     *
69
     * @var array
70
     */
71
    private $genericTypes = [];
72
73
    /**
74
     * Various options a type might need to reference
75
     *
76
     * For example, a DateTime object might want to store formatting options
77
     *
78
     * @var array
79
     */
80
    private $options = [];
81
82
    /**
83
     * Constructor
84
     *
85
     * @param string $type
86
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
87
     */
88 35
    public function __construct(string $type)
89
    {
90 35
        $this->fullType = (string) str_replace(' ', '', $type);
91
92 35
        $this->parseType($this->fullType);
93 34
    }
94
95
    /**
96
     * Create a new instance from a variable
97
     *
98
     * @param mixed $variable
99
     * @return PhpType
100
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
101
     */
102 8
    public static function createFromVariable($variable): PhpType
103
    {
104 8
        return is_object($variable) ? new self(get_class($variable)) : new self(gettype($variable));
105
    }
106
107
    /**
108
     * Recursively parse type.  If generics are found, this will create
109
     * new PhpTypes.
110
     *
111
     * @param string $type
112
     * @return void
113
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
114
     */
115 35
    private function parseType(string $type): void
116
    {
117 35
        if (false === strpos($type, '<')) {
118 34
            $this->setType($type);
119
120 34
            return;
121
        }
122
123
        // get start and end positions of generic
124 7
        $start = strpos($type, '<');
125 7
        $end = strrpos($type, '>');
126
127 7
        if (false === $end) {
128 1
            throw new MalformedTypeException('Could not find ending ">" for generic type');
129
        }
130
131
        // get generic types
132 6
        $genericTypes = substr($type, $start + 1, $end - $start - 1);
133
134
        // set the main type
135 6
        $this->setType(substr($type, 0, $start));
136
137
        // iterate over subtype to determine if format is <type> or <key, type>
138 6
        $depth = 0;
139 6
        $type = '';
140 6
        foreach (str_split($genericTypes) as $char) {
141
            // stepping into another generic type
142 6
            if ('<' === $char) {
143 2
                $depth++;
144
            }
145
146
            // stepping out of generic type
147 6
            if ('>' === $char) {
148 2
                $depth--;
149
            }
150
151
            // we only care about commas for the initial list of generics
152 6
            if (',' === $char && 0 === $depth) {
153
                // add new type to list
154 4
                $this->genericTypes[] = new PhpType($type);
155
156
                // reset type
157 4
                $type = '';
158
159 4
                continue;
160
            }
161
162
            // write character key
163 6
            $type .= $char;
164
        }
165
166 6
        $this->genericTypes[] = new PhpType($type);
167 6
    }
168
169
    /**
170
     * Create a type enum and set the class if necessary
171
     *
172
     * @param string $type
173
     * @return void
174
     */
175 34
    private function setType(string $type): void
176
    {
177 34
        $this->type = TypeToken::normalizeType($type);
178
179 34
        if ($this->isObject()) {
180 8
            $this->class = 'object' === $type ? stdClass::class : $type;
181 29
        } elseif (false === strpos($this->fullType, '<')) {
182 29
            $this->fullType = (string) $this->type;
183
        }
184 34
    }
185
186
    /**
187
     * Returns an array of generic types
188
     *
189
     * @return array
190
     */
191 4
    public function getGenerics(): array
192
    {
193 4
        return $this->genericTypes;
194
    }
195
196
    /**
197
     * Returns the class as a string or null if there isn't a class
198
     *
199
     * @return string
200
     */
201 5
    public function getClass(): ?string
202
    {
203 5
        return $this->class;
204
    }
205
206
    /**
207
     * Returns true if this is a string
208
     *
209
     * @return bool
210
     */
211 1
    public function isString(): bool
212
    {
213 1
        return $this->type === TypeToken::STRING;
214
    }
215
216
    /**
217
     * Returns true if this is an integer
218
     *
219
     * @return bool
220
     */
221 2
    public function isInteger(): bool
222
    {
223 2
        return $this->type === TypeToken::INTEGER;
224
    }
225
226
    /**
227
     * Returns true if this is a float
228
     *
229
     * @return bool
230
     */
231 2
    public function isFloat(): bool
232
    {
233 2
        return $this->type === TypeToken::FLOAT;
234
    }
235
236
    /**
237
     * Returns true if this is a boolean
238
     *
239
     * @return bool
240
     */
241 2
    public function isBoolean(): bool
242
    {
243 2
        return $this->type === TypeToken::BOOLEAN;
244
    }
245
246
    /**
247
     * Returns true if this is an array
248
     *
249
     * @return bool
250
     */
251 4
    public function isArray(): bool
252
    {
253 4
        return $this->type === TypeToken::ARRAY;
254
    }
255
256
    /**
257
     * Returns true if this is an object
258
     *
259
     * @return bool
260
     */
261 34
    public function isObject(): bool
262
    {
263 34
        return $this->type === TypeToken::OBJECT;
264
    }
265
266
    /**
267
     * Returns true if this is null
268
     *
269
     * @return bool
270
     */
271 2
    public function isNull(): bool
272
    {
273 2
        return $this->type === TypeToken::NULL;
274
    }
275
276
    /**
277
     * Returns true if this is a resource
278
     *
279
     * @return bool
280
     */
281 1
    public function isResource(): bool
282
    {
283 1
        return $this->type === TypeToken::RESOURCE;
284
    }
285
286
    /**
287
     * Returns true if the type could be anything
288
     *
289
     * @return bool
290
     */
291 1
    public function isWildcard(): bool
292
    {
293 1
        return $this->type === TypeToken::WILDCARD;
294
    }
295
296
    /**
297
     * Returns an array of extra options
298
     *
299
     * @return array
300
     */
301 1
    public function getOptions(): array
302
    {
303 1
        return $this->options;
304
    }
305
306
    /**
307
     * Sets extra options on this type
308
     *
309
     * @param array $options
310
     * @return void
311
     */
312 2
    public function setOptions(array $options): void
313
    {
314 2
        $this->options = $options;
315 2
    }
316
317
    /**
318
     * Returns a unique identifying key for this type based on
319
     * the full type and options
320
     *
321
     * @return string
322
     */
323 3
    public function getUniqueKey(): string
324
    {
325 3
        return [] === $this->options
326 2
            ? $this->fullType
327 3
            : $this->fullType.serialize($this->options);
328
    }
329
330
    /**
331
     * Return the initial type including generics
332
     *
333
     * @return string
334
     */
335 15
    public function __toString(): string
336
    {
337 15
        return $this->fullType;
338
    }
339
}
340