Passed
Push — master ( b9c988...e54fe2 )
by Nate
01:46
created

TypeToken   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 1
dl 0
loc 389
ccs 110
cts 110
cp 1
rs 6.96
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A create() 0 8 2
A createFromVariable() 0 6 2
A getRawType() 0 4 1
A getPhpType() 0 4 1
A getGenerics() 0 4 1
A isA() 0 16 4
A isString() 0 4 1
A isInteger() 0 4 1
A isFloat() 0 4 1
A isBoolean() 0 4 1
A isScalar() 0 8 5
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 __toString() 0 4 1
B parseType() 0 53 8
A setTypes() 0 25 5
C getNormalizedType() 0 27 13

How to fix   Complexity   

Complex Class

Complex classes like TypeToken often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypeToken, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\PhpType;
10
11
use stdClass;
12
use Tebru\PhpType\Exception\MalformedTypeException;
13
14
/**
15
 * Class TypeToken
16
 *
17
 * Wrapper around core php types and custom types.  It can be used as simply as
18
 *
19
 *     new TypeToken('string');
20
 *
21
 * To create a string type.
22
 *
23
 * This class also allows us to fake generic types.  The syntax to
24
 * represent generics uses angle brackets <>.
25
 *
26
 * For example:
27
 *
28
 *     array<int>
29
 *
30
 * Would represent an array of ints.
31
 *
32
 *     array<string, int>
33
 *
34
 * Would represent an array using string keys and int values.
35
 *
36
 * They can be combined, like so
37
 *
38
 *     array<string, array<int>>
39
 *
40
 * To represent a array with string keys and an array of ints as values.
41
 *
42
 * @author Nate Brunette <[email protected]>
43
 */
44
final class TypeToken
45
{
46
    public const STRING = 'string';
47
    public const INTEGER = 'integer';
48
    public const FLOAT = 'float';
49
    public const BOOLEAN = 'boolean';
50
    public const HASH = 'array';
51
    public const OBJECT = 'object';
52
    public const NULL = 'null';
53
    public const RESOURCE = 'resource';
54
    public const WILDCARD = '?';
55
56
    /**
57
     * The full initial type
58
     *
59
     * @var string
60
     */
61
    public $fullTypeString;
62
63
    /**
64
     * The core php type (string, int, etc) or class if object
65
     *
66
     * @var string
67
     */
68
    public $rawType;
69
70
    /**
71
     * The core php type (string, int, object, etc)
72
     *
73
     * @var string
74
     */
75
    public $phpType;
76
77
    /**
78
     * An array of parent classes and interfaces that a class implements
79
     *
80
     * @var array
81
     */
82
    public $parents = [];
83
84
    /**
85
     * Generic types, if they exist
86
     *
87
     * @var array
88
     */
89
    public $genericTypes = [];
90
91
    /**
92
     * An array of cached types
93
     *
94
     * @var TypeToken[]
95
     */
96
    public static $types = [];
97
98
    /**
99
     * Constructor
100
     *
101
     * @param string $type
102
     */
103 44
    public function __construct(string $type)
104
    {
105 44
        $type = \trim($type);
106 44
        $this->fullTypeString = $type;
107 44
        $this->parseType($type);
108 43
    }
109
110
    /**
111
     * Singleton factory for creating types
112
     *
113
     * @param string $type
114
     * @return TypeToken
115
     */
116 16
    public static function create(string $type): TypeToken
117
    {
118 16
        if (!isset(self::$types[$type])) {
119 10
            self::$types[$type] = new static($type);
120
        }
121
122 16
        return self::$types[$type];
123
    }
124
125
    /**
126
     * Create a new instance from a variable
127
     *
128
     * @param mixed $variable
129
     * @return TypeToken
130
     */
131 9
    public static function createFromVariable($variable): TypeToken
132
    {
133 9
        $type = \is_object($variable) ? \get_class($variable) : \gettype($variable);
134
135 9
        return self::create($type);
136
    }
137
138
    /**
139
     * Returns the class if an object, or the type as a string
140
     *
141
     * @return string
142
     */
143 8
    public function getRawType(): string
144
    {
145 8
        return $this->rawType;
146
    }
147
148
    /**
149
     * Returns the core php type
150
     *
151
     * @return string
152
     */
153 2
    public function getPhpType(): string
154
    {
155 2
        return $this->phpType;
156
    }
157
158
    /**
159
     * Returns an array of generic types
160
     *
161
     * @return TypeToken[]
162
     */
163 35
    public function getGenerics(): array
164
    {
165 35
        return $this->genericTypes;
166
    }
167
168
    /**
169
     * Returns true if the type matches the class, parent, full type, or one of the interfaces
170
     *
171
     * @param string $type
172
     * @return bool
173
     */
174 4
    public function isA(string $type): bool
175
    {
176 4
        if ($this->rawType === $type) {
177 4
            return true;
178
        }
179
180 2
        if ($this->fullTypeString === $type) {
181 1
            return true;
182
        }
183
184 1
        if (\in_array($type, $this->parents, true)) {
185 1
            return true;
186
        }
187
188 1
        return false;
189
    }
190
191
    /**
192
     * Returns true if this is a string
193
     *
194
     * @return bool
195
     */
196 1
    public function isString(): bool
197
    {
198 1
        return $this->phpType === self::STRING;
199
    }
200
201
    /**
202
     * Returns true if this is an integer
203
     *
204
     * @return bool
205
     */
206 2
    public function isInteger(): bool
207
    {
208 2
        return $this->phpType === self::INTEGER;
209
    }
210
211
    /**
212
     * Returns true if this is a float
213
     *
214
     * @return bool
215
     */
216 2
    public function isFloat(): bool
217
    {
218 2
        return $this->phpType === self::FLOAT;
219
    }
220
221
    /**
222
     * Returns true if this is a boolean
223
     *
224
     * @return bool
225
     */
226 2
    public function isBoolean(): bool
227
    {
228 2
        return $this->phpType === self::BOOLEAN;
229
    }
230
231
    /**
232
     * Returns true if the type is a scalar type
233
     *
234
     * @return bool
235
     */
236 6
    public function isScalar(): bool
237
    {
238 6
        return $this->phpType === self::INTEGER
239 5
            || $this->phpType === self::FLOAT
240 4
            || $this->phpType === self::STRING
241 3
            || $this->phpType === self::BOOLEAN
242 6
            || $this->phpType === self::NULL;
243
    }
244
245
    /**
246
     * Returns true if this is an array
247
     *
248
     * @return bool
249
     */
250 4
    public function isArray(): bool
251
    {
252 4
        return $this->phpType === self::HASH;
253
    }
254
255
    /**
256
     * Returns true if this is an object
257
     *
258
     * @return bool
259
     */
260 43
    public function isObject(): bool
261
    {
262 43
        return $this->phpType === self::OBJECT;
263
    }
264
265
    /**
266
     * Returns true if this is null
267
     *
268
     * @return bool
269
     */
270 2
    public function isNull(): bool
271
    {
272 2
        return $this->phpType === self::NULL;
273
    }
274
275
    /**
276
     * Returns true if this is a resource
277
     *
278
     * @return bool
279
     */
280 1
    public function isResource(): bool
281
    {
282 1
        return $this->phpType === self::RESOURCE;
283
    }
284
285
    /**
286
     * Returns true if the type could be anything
287
     *
288
     * @return bool
289
     */
290 1
    public function isWildcard(): bool
291
    {
292 1
        return $this->phpType === self::WILDCARD;
293
    }
294
295
    /**
296
     * Return the initial type including generics
297
     *
298
     * @return string
299
     */
300 15
    public function __toString(): string
301
    {
302 15
        return $this->fullTypeString;
303
    }
304
305
    /**
306
     * Recursively parse type.  If generics are found, this will create
307
     * new PhpTypes.
308
     *
309
     * @param string $type
310
     * @return void
311
     * @throws \Tebru\PhpType\Exception\MalformedTypeException If the type cannot be processed
312
     */
313 44
    private function parseType(string $type): void
314
    {
315 44
        $start = \strpos($type, '<');
316 44
        if ($start === false) {
317 40
            $this->setTypes($type);
318 40
            return;
319
        }
320
321
        // get start and end positions of generic
322 8
        $end = \strrpos($type, '>');
323 8
        if ($end === false) {
324 1
            throw new MalformedTypeException('Could not find ending ">" for generic type');
325
        }
326
327 7
        $originalType = $type;
328
329
        // get generic types
330 7
        $generics = \substr($type, $start + 1, $end - $start - 1);
331
332
        // iterate over subtype to determine if format is <type> or <key, type>
333 7
        $depth = 0;
334 7
        $type = '';
335 7
        foreach (\str_split($generics) as $char) {
336
            // stepping into another generic type
337 7
            if ($char === '<') {
338 2
                $depth++;
339
            }
340
341
            // stepping out of generic type
342 7
            if ($char === '>') {
343 2
                $depth--;
344
            }
345
346
            // if the character is not a comma, or we're not on the first level
347
            // write the character to the buffer and continue loop
348 7
            if ($char !== ',' || $depth !== 0) {
349 7
                $type .= $char;
350 7
                continue;
351
            }
352
353
            // add new type to list
354 3
            $this->genericTypes[] = static::create($type);
355
356
            // reset type buffer
357 3
            $type = '';
358
        }
359
360
        // add final type
361 7
        $this->genericTypes[] = static::create($type);
362
363
        // set the main type
364 7
        $this->setTypes(\substr($originalType, 0, $start));
365 7
    }
366
367
    /**
368
     * Create a type enum and set the class if necessary
369
     *
370
     * @param string $rawType
371
     * @return void
372
     */
373 43
    private function setTypes(string $rawType): void
374
    {
375 43
        $this->phpType = $this->getNormalizedType($rawType);
376
377
        // if we're not working with an object, set the raw type to
378
        // the core php type so we can make sure it's normalized
379 43
        if (!$this->isObject()) {
380 34
            $this->rawType = $this->phpType;
381
382
            // if there aren't any generics, overwrite full type as well
383 34
            if ($this->getGenerics() === []) {
384 31
                $this->fullTypeString = $this->rawType;
385
            }
386 34
            return;
387
        }
388
389
        // use \stdClass as the class name for generic objects
390 10
        $this->rawType = self::OBJECT === $rawType ? stdClass::class : $rawType;
391
392
        // if we're dealing with a real class, get parents and interfaces so
393
        // it's easy to check if the type is an instance of another
394 10
        if (\class_exists($rawType)) {
395 8
            $this->parents = \array_merge(\class_parents($this->rawType), \class_implements($this->rawType));
396
        }
397 10
    }
398
399
    /**
400
     * Get a normalized core php type
401
     *
402
     * @param string $type
403
     * @return string
404
     */
405 43
    private function getNormalizedType(string $type): string
406
    {
407
        switch ($type) {
408 43
            case 'string':
409 5
                return self::STRING;
410 39
            case 'int':
411 35
            case 'integer':
412 8
                return self::INTEGER;
413 33
            case 'double':
414 31
            case 'float':
415 4
                return self::FLOAT;
416 29
            case 'bool':
417 28
            case 'boolean':
418 5
                return self::BOOLEAN;
419 25
            case 'array':
420 11
                return self::HASH;
421 15
            case 'null':
422 14
            case 'NULL':
423 3
                return self::NULL;
424 12
            case 'resource':
425 1
                return self::RESOURCE;
426 11
            case '?':
427 1
                return self::WILDCARD;
428
            default:
429 10
                return self::OBJECT;
430
        }
431
    }
432
}
433