Passed
Push — master ( 51c859...9f702c )
by Nate
02:52
created

PhpType::setType()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 4
nop 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
 *     ArrayList<int>
28
 *
29
 * Would represent an ArrayList of ints.
30
 *
31
 *     HashMap<string, int>
32
 *
33
 * Would represent a HashMap using string keys and int values.
34
 *
35
 * They can be combined, like so
36
 *
37
 *     HashMap<string, ArrayList<int>>
38
 *
39
 * To represent a HashMap with string keys and an ArrayList 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
    public function __construct(string $type)
89
    {
90
        $this->fullType = (string) str_replace(' ', '', $type);
91
92
        $this->parseType($this->fullType);
93
    }
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
    public static function createFromVariable($variable): PhpType
103
    {
104
        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
    private function parseType(string $type): void
116
    {
117
        if (false === strpos($type, '<')) {
118
            $this->setType($type);
119
120
            return;
121
        }
122
123
        // get start and end positions of generic
124
        $start = strpos($type, '<');
125
        $end = strrpos($type, '>');
126
127
        if (false === $end) {
128
            throw new MalformedTypeException('Could not find ending ">" for generic type');
129
        }
130
131
        // get generic types
132
        $genericTypes = substr($type, $start + 1, $end - $start - 1);
133
134
        // set the main type
135
        $this->setType(substr($type, 0, $start));
136
137
        // iterate over subtype to determine if format is <type> or <key, type>
138
        $depth = 0;
139
        $type = '';
140
        foreach (str_split($genericTypes) as $char) {
141
            // stepping into another generic type
142
            if ('<' === $char) {
143
                $depth++;
144
            }
145
146
            // stepping out of generic type
147
            if ('>' === $char) {
148
                $depth--;
149
            }
150
151
            // we only care about commas for the initial list of generics
152
            if (',' === $char && 0 === $depth) {
153
                // add new type to list
154
                $this->genericTypes[] = new PhpType($type);
155
156
                // reset type
157
                $type = '';
158
159
                continue;
160
            }
161
162
            // write character key
163
            $type .= $char;
164
        }
165
166
        $this->genericTypes[] = new PhpType($type);
167
    }
168
169
    /**
170
     * Create a type enum and set the class if necessary
171
     *
172
     * @param string $type
173
     * @return void
174
     */
175
    private function setType(string $type): void
176
    {
177
        $this->type = TypeToken::normalizeType($type);
178
179
        if ($this->isObject()) {
180
            $this->class = 'object' === $type ? stdClass::class : $type;
181
        } elseif (false === strpos($this->fullType, '<')) {
182
            $this->fullType = (string) $this->type;
183
        }
184
    }
185
186
    /**
187
     * Returns an array of generic types
188
     *
189
     * @return array
190
     */
191
    public function getGenerics(): array
192
    {
193
        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
    public function getClass(): ?string
202
    {
203
        return $this->class;
204
    }
205
206
    /**
207
     * Returns true if this is a string
208
     *
209
     * @return bool
210
     */
211
    public function isString(): bool
212
    {
213
        return $this->type === TypeToken::STRING;
214
    }
215
216
    /**
217
     * Returns true if this is an integer
218
     *
219
     * @return bool
220
     */
221
    public function isInteger(): bool
222
    {
223
        return $this->type === TypeToken::INTEGER;
224
    }
225
226
    /**
227
     * Returns true if this is a float
228
     *
229
     * @return bool
230
     */
231
    public function isFloat(): bool
232
    {
233
        return $this->type === TypeToken::FLOAT;
234
    }
235
236
    /**
237
     * Returns true if this is a boolean
238
     *
239
     * @return bool
240
     */
241
    public function isBoolean(): bool
242
    {
243
        return $this->type === TypeToken::BOOLEAN;
244
    }
245
246
    /**
247
     * Returns true if this is an array
248
     *
249
     * @return bool
250
     */
251
    public function isArray(): bool
252
    {
253
        return $this->type === TypeToken::ARRAY;
254
    }
255
256
    /**
257
     * Returns true if this is an object
258
     *
259
     * @return bool
260
     */
261
    public function isObject(): bool
262
    {
263
        return $this->type === TypeToken::OBJECT;
264
    }
265
266
    /**
267
     * Returns true if this is null
268
     *
269
     * @return bool
270
     */
271
    public function isNull(): bool
272
    {
273
        return $this->type === TypeToken::NULL;
274
    }
275
276
    /**
277
     * Returns true if this is a resource
278
     *
279
     * @return bool
280
     */
281
    public function isResource(): bool
282
    {
283
        return $this->type === TypeToken::RESOURCE;
284
    }
285
286
    /**
287
     * Returns true if the type could be anything
288
     *
289
     * @return bool
290
     */
291
    public function isWildcard(): bool
292
    {
293
        return $this->type === TypeToken::WILDCARD;
294
    }
295
296
    /**
297
     * Returns an array of extra options
298
     *
299
     * @return array
300
     */
301
    public function getOptions(): array
302
    {
303
        return $this->options;
304
    }
305
306
    /**
307
     * Sets extra options on this type
308
     *
309
     * @param array $options
310
     * @return void
311
     */
312
    public function setOptions(array $options): void
313
    {
314
        $this->options = $options;
315
    }
316
317
    /**
318
     * Return the initial type including generics
319
     *
320
     * @return string
321
     */
322
    public function __toString(): string
323
    {
324
        return $this->fullType;
325
    }
326
}
327