Completed
Push — master ( 1f9c9a...dbd6f1 )
by Nate
04:38
created

DefaultPhpType   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 2
dl 0
loc 288
ccs 68
cts 68
cp 1
rs 9.8
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A createFromVariable() 0 4 2
B parseType() 0 53 8
A setType() 0 10 4
A getGenerics() 0 4 1
A getType() 0 4 2
A isString() 0 4 1
A isInteger() 0 4 1
A isFloat() 0 4 1
A isBoolean() 0 4 1
A isArray() 0 4 1
A isObject() 0 4 1
A isNull() 0 4 1
A isResource() 0 4 1
A isWildcard() 0 4 1
A getOptions() 0 4 1
A getUniqueKey() 0 6 2
A __toString() 0 4 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\Internal;
8
9
use stdClass;
10
use Tebru\Gson\Exception\MalformedTypeException;
11
use Tebru\Gson\PhpType;
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 DefaultPhpType implements 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
     * @param array $options
87
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
88
     */
89 40
    public function __construct(string $type, array $options = [])
90
    {
91 40
        $this->options = $options;
92 40
        $this->fullType = (string) str_replace(' ', '', $type);
93
94 40
        $this->parseType($this->fullType);
95 39
    }
96
97
    /**
98
     * Create a new instance from a variable
99
     *
100
     * @param mixed $variable
101
     * @return PhpType
102
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
103
     */
104 8
    public static function createFromVariable($variable): PhpType
105
    {
106 8
        return is_object($variable) ? new self(get_class($variable)) : new self(gettype($variable));
107
    }
108
109
    /**
110
     * Recursively parse type.  If generics are found, this will create
111
     * new PhpTypes.
112
     *
113
     * @param string $type
114
     * @return void
115
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
116
     */
117 40
    private function parseType(string $type): void
118
    {
119 40
        if (false === strpos($type, '<')) {
120 39
            $this->setType($type);
121
122 39
            return;
123
        }
124
125
        // get start and end positions of generic
126 7
        $start = strpos($type, '<');
127 7
        $end = strrpos($type, '>');
128
129 7
        if (false === $end) {
130 1
            throw new MalformedTypeException('Could not find ending ">" for generic type');
131
        }
132
133
        // get generic types
134 6
        $genericTypes = substr($type, $start + 1, $end - $start - 1);
135
136
        // set the main type
137 6
        $this->setType(substr($type, 0, $start));
138
139
        // iterate over subtype to determine if format is <type> or <key, type>
140 6
        $depth = 0;
141 6
        $type = '';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $type. This often makes code more readable.
Loading history...
142 6
        foreach (str_split($genericTypes) as $char) {
143
            // stepping into another generic type
144 6
            if ('<' === $char) {
145 2
                $depth++;
146
            }
147
148
            // stepping out of generic type
149 6
            if ('>' === $char) {
150 2
                $depth--;
151
            }
152
153
            // we only care about commas for the initial list of generics
154 6
            if (',' === $char && 0 === $depth) {
155
                // add new type to list
156 4
                $this->genericTypes[] = new DefaultPhpType($type);
157
158
                // reset type
159 4
                $type = '';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $type. This often makes code more readable.
Loading history...
160
161 4
                continue;
162
            }
163
164
            // write character key
165 6
            $type .= $char;
166
        }
167
168 6
        $this->genericTypes[] = new DefaultPhpType($type);
169 6
    }
170
171
    /**
172
     * Create a type enum and set the class if necessary
173
     *
174
     * @param string $type
175
     * @return void
176
     */
177 39
    private function setType(string $type): void
178
    {
179 39
        $this->type = TypeToken::normalizeType($type);
180
181 39
        if ($this->isObject()) {
182 8
            $this->class = 'object' === $type ? stdClass::class : $type;
183 34
        } elseif (false === strpos($this->fullType, '<')) {
184 34
            $this->fullType = (string) $this->type;
185
        }
186 39
    }
187
188
    /**
189
     * Returns an array of generic types
190
     *
191
     * @return array
192
     */
193 4
    public function getGenerics(): array
194
    {
195 4
        return $this->genericTypes;
196
    }
197
198
    /**
199
     * Returns the class if an object, or the type as a string
200
     *
201
     * @return string
202
     */
203 5
    public function getType(): ?string
204
    {
205 5
        return $this->isObject() ? $this->class : $this->fullType;
206
    }
207
208
    /**
209
     * Returns true if this is a string
210
     *
211
     * @return bool
212
     */
213 1
    public function isString(): bool
214
    {
215 1
        return $this->type === TypeToken::STRING;
216
    }
217
218
    /**
219
     * Returns true if this is an integer
220
     *
221
     * @return bool
222
     */
223 2
    public function isInteger(): bool
224
    {
225 2
        return $this->type === TypeToken::INTEGER;
226
    }
227
228
    /**
229
     * Returns true if this is a float
230
     *
231
     * @return bool
232
     */
233 2
    public function isFloat(): bool
234
    {
235 2
        return $this->type === TypeToken::FLOAT;
236
    }
237
238
    /**
239
     * Returns true if this is a boolean
240
     *
241
     * @return bool
242
     */
243 2
    public function isBoolean(): bool
244
    {
245 2
        return $this->type === TypeToken::BOOLEAN;
246
    }
247
248
    /**
249
     * Returns true if this is an array
250
     *
251
     * @return bool
252
     */
253 4
    public function isArray(): bool
254
    {
255 4
        return $this->type === TypeToken::ARRAY;
256
    }
257
258
    /**
259
     * Returns true if this is an object
260
     *
261
     * @return bool
262
     */
263 39
    public function isObject(): bool
264
    {
265 39
        return $this->type === TypeToken::OBJECT;
266
    }
267
268
    /**
269
     * Returns true if this is null
270
     *
271
     * @return bool
272
     */
273 2
    public function isNull(): bool
274
    {
275 2
        return $this->type === TypeToken::NULL;
276
    }
277
278
    /**
279
     * Returns true if this is a resource
280
     *
281
     * @return bool
282
     */
283 1
    public function isResource(): bool
284
    {
285 1
        return $this->type === TypeToken::RESOURCE;
286
    }
287
288
    /**
289
     * Returns true if the type could be anything
290
     *
291
     * @return bool
292
     */
293 1
    public function isWildcard(): bool
294
    {
295 1
        return $this->type === TypeToken::WILDCARD;
296
    }
297
298
    /**
299
     * Returns an array of extra options
300
     *
301
     * @return array
302
     */
303 1
    public function getOptions(): array
304
    {
305 1
        return $this->options;
306
    }
307
308
    /**
309
     * Returns a unique identifying key for this type based on
310
     * the full type and options
311
     *
312
     * @return string
313
     */
314 3
    public function getUniqueKey(): string
315
    {
316 3
        return [] === $this->options
317 2
            ? $this->fullType
318 3
            : $this->fullType.serialize($this->options);
319
    }
320
321
    /**
322
     * Return the initial type including generics
323
     *
324
     * @return string
325
     */
326 16
    public function __toString(): string
327
    {
328 16
        return $this->fullType;
329
    }
330
}
331