Completed
Push — master ( 5a86e8...bb2d28 )
by Nate
03:17
created

DefaultPhpType::isA()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 9
cts 9
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
crap 4
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
     * An array of interfaces that a class implements
68
     *
69
     * @var array
70
     */
71
    private $interfaces = [];
72
73
    /**
74
     * Generic types, if they exist
75
     *
76
     * @var array
77
     */
78
    private $genericTypes = [];
79
80
    /**
81
     * Various options a type might need to reference
82
     *
83
     * For example, a DateTime object might want to store formatting options
84
     *
85
     * @var array
86
     */
87
    private $options = [];
88
89
    /**
90
     * Constructor
91
     *
92
     * @param string $type
93
     * @param array $options
94
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
95
     */
96 51
    public function __construct(string $type, array $options = [])
97
    {
98 51
        $this->options = $options;
99 51
        $this->fullType = (string) str_replace(' ', '', $type);
100
101 51
        $this->parseType($this->fullType);
102 50
    }
103
104
    /**
105
     * Create a new instance from a variable
106
     *
107
     * @param mixed $variable
108
     * @return PhpType
109
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
110
     */
111 8
    public static function createFromVariable($variable): PhpType
112
    {
113 8
        return is_object($variable) ? new self(get_class($variable)) : new self(gettype($variable));
114
    }
115
116
    /**
117
     * Recursively parse type.  If generics are found, this will create
118
     * new PhpTypes.
119
     *
120
     * @param string $type
121
     * @return void
122
     * @throws \Tebru\Gson\Exception\MalformedTypeException If the type cannot be parsed
123
     */
124 51
    private function parseType(string $type): void
125
    {
126 51
        if (false === strpos($type, '<')) {
127 50
            $this->setType($type);
128
129 50
            return;
130
        }
131
132
        // get start and end positions of generic
133 9
        $start = strpos($type, '<');
134 9
        $end = strrpos($type, '>');
135
136 9
        if (false === $end) {
137 1
            throw new MalformedTypeException('Could not find ending ">" for generic type');
138
        }
139
140
        // get generic types
141 8
        $genericTypes = substr($type, $start + 1, $end - $start - 1);
142
143
        // set the main type
144 8
        $this->setType(substr($type, 0, $start));
145
146
        // iterate over subtype to determine if format is <type> or <key, type>
147 8
        $depth = 0;
148 8
        $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...
149 8
        foreach (str_split($genericTypes) as $char) {
150
            // stepping into another generic type
151 8
            if ('<' === $char) {
152 2
                $depth++;
153
            }
154
155
            // stepping out of generic type
156 8
            if ('>' === $char) {
157 2
                $depth--;
158
            }
159
160
            // we only care about commas for the initial list of generics
161 8
            if (',' === $char && 0 === $depth) {
162
                // add new type to list
163 4
                $this->genericTypes[] = new DefaultPhpType($type);
164
165
                // reset type
166 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...
167
168 4
                continue;
169
            }
170
171
            // write character key
172 8
            $type .= $char;
173
        }
174
175 8
        $this->genericTypes[] = new DefaultPhpType($type);
176 8
    }
177
178
    /**
179
     * Create a type enum and set the class if necessary
180
     *
181
     * @param string $type
182
     * @return void
183
     */
184 50
    private function setType(string $type): void
185
    {
186 50
        $this->type = TypeToken::normalizeType($type);
187
188 50
        if ($this->isObject()) {
189 13
            $this->class = TypeToken::OBJECT === $type ? stdClass::class : $type;
190 13
            $this->interfaces = class_implements($this->class);
191 40
        } elseif (false === strpos($this->fullType, '<')) {
192 40
            $this->fullType = (string) $this->type;
193
        }
194 50
    }
195
196
    /**
197
     * Returns an array of generic types
198
     *
199
     * @return array
200
     */
201 4
    public function getGenerics(): array
202
    {
203 4
        return $this->genericTypes;
204
    }
205
206
    /**
207
     * Returns the class if an object, or the type as a string
208
     *
209
     * @return string
210
     */
211 14
    public function getType(): ?string
212
    {
213 14
        return $this->isObject() ? $this->class : $this->fullType;
214
    }
215
216
    /**
217
     * Returns true if the type matches the class, parent, full type, or one of the interfaces
218
     *
219
     * @param string $type
220
     * @return bool
221
     */
222 6
    public function isA(string $type): bool
223
    {
224 6
        $currentType = $this->getType();
225 6
        if ($currentType === $type) {
226 4
            return true;
227
        }
228
229 4
        if (in_array($type, $this->interfaces, true)) {
230 2
            return true;
231
        }
232
233 3
        if (is_subclass_of($currentType, $type)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return is_subclass_of($currentType, $type);.
Loading history...
234 1
            return true;
235
        }
236
237 2
        return false;
238
    }
239
240
    /**
241
     * Returns true if this is a string
242
     *
243
     * @return bool
244
     */
245 1
    public function isString(): bool
246
    {
247 1
        return $this->type === TypeToken::STRING;
248
    }
249
250
    /**
251
     * Returns true if this is an integer
252
     *
253
     * @return bool
254
     */
255 2
    public function isInteger(): bool
256
    {
257 2
        return $this->type === TypeToken::INTEGER;
258
    }
259
260
    /**
261
     * Returns true if this is a float
262
     *
263
     * @return bool
264
     */
265 2
    public function isFloat(): bool
266
    {
267 2
        return $this->type === TypeToken::FLOAT;
268
    }
269
270
    /**
271
     * Returns true if this is a boolean
272
     *
273
     * @return bool
274
     */
275 2
    public function isBoolean(): bool
276
    {
277 2
        return $this->type === TypeToken::BOOLEAN;
278
    }
279
280
    /**
281
     * Returns true if this is an array
282
     *
283
     * @return bool
284
     */
285 4
    public function isArray(): bool
286
    {
287 4
        return $this->type === TypeToken::ARRAY;
288
    }
289
290
    /**
291
     * Returns true if this is an object
292
     *
293
     * @return bool
294
     */
295 50
    public function isObject(): bool
296
    {
297 50
        return $this->type === TypeToken::OBJECT;
298
    }
299
300
    /**
301
     * Returns true if this is null
302
     *
303
     * @return bool
304
     */
305 2
    public function isNull(): bool
306
    {
307 2
        return $this->type === TypeToken::NULL;
308
    }
309
310
    /**
311
     * Returns true if this is a resource
312
     *
313
     * @return bool
314
     */
315 1
    public function isResource(): bool
316
    {
317 1
        return $this->type === TypeToken::RESOURCE;
318
    }
319
320
    /**
321
     * Returns true if the type could be anything
322
     *
323
     * @return bool
324
     */
325 1
    public function isWildcard(): bool
326
    {
327 1
        return $this->type === TypeToken::WILDCARD;
328
    }
329
330
    /**
331
     * Returns an array of extra options
332
     *
333
     * @return array
334
     */
335 1
    public function getOptions(): array
336
    {
337 1
        return $this->options;
338
    }
339
340
    /**
341
     * Returns a unique identifying key for this type based on
342
     * the full type and options
343
     *
344
     * @return string
345
     */
346 3
    public function getUniqueKey(): string
347
    {
348 3
        return [] === $this->options
349 2
            ? $this->fullType
350 3
            : $this->fullType.serialize($this->options);
351
    }
352
353
    /**
354
     * Return the initial type including generics
355
     *
356
     * @return string
357
     */
358 16
    public function __toString(): string
359
    {
360 16
        return $this->fullType;
361
    }
362
}
363