GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#56)
by Arthur
01:42 queued 39s
created

Property   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 109
dl 0
loc 294
rs 5.04
c 0
b 0
f 0
wmc 57

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setVisible() 0 3 1
A isVisible() 0 3 1
A isInitialized() 0 3 1
A castCollection() 0 25 5
A nullable() 0 3 1
A setDefault() 0 3 1
A isValidGenericCollection() 0 15 4
A getValue() 0 7 3
A set() 0 13 4
A shouldBeCastToCollection() 0 17 5
A getValueFromReflection() 0 3 1
A isValidType() 0 19 6
A getTypes() 0 3 1
A cast() 0 19 4
A getFqn() 0 3 1
A setNullable() 0 3 1
A setImmutable() 0 3 1
A resolveTypeDefinition() 0 35 6
A immutable() 0 3 1
A getDefault() 0 3 1
A setInitialized() 0 3 1
A assertTypeEquals() 0 12 5
A __construct() 0 5 1
A getName() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Property 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.

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 Property, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spatie\DataTransferObject;
6
7
use ReflectionProperty;
8
use Spatie\DataTransferObject\Contracts\DtoContract;
9
use Spatie\DataTransferObject\Contracts\PropertyContract;
10
use Spatie\DataTransferObject\Exceptions\InvalidTypeDtoException;
11
12
class Property implements PropertyContract
13
{
14
    /** @var array */
15
    protected const TYPE_MAPPING = [
16
        'int' => 'integer',
17
        'bool' => 'boolean',
18
        'float' => 'double',
19
    ];
20
21
    /** @var bool */
22
    protected $hasTypeDeclaration = false;
23
24
    /** @var bool */
25
    protected $nullable = false;
26
27
    /** @var bool */
28
    protected $initialised = false;
29
30
    /** @var bool */
31
    protected $immutable = false;
32
33
    /** @var bool */
34
    protected $visible = true;
35
36
    /** @var array */
37
    protected $types = [];
38
39
    /** @var array */
40
    protected $arrayTypes = [];
41
42
    /** @var mixed */
43
    protected $default;
44
45
    /** @var mixed */
46
    protected $value;
47
48
    /** @var ReflectionProperty */
49
    protected $reflection;
50
51
    public function __construct(ReflectionProperty $reflectionProperty)
52
    {
53
        $this->reflection = $reflectionProperty;
54
55
        $this->resolveTypeDefinition();
56
    }
57
58
    protected function resolveTypeDefinition()
59
    {
60
        $docComment = $this->reflection->getDocComment();
61
62
        if (! $docComment) {
63
            $this->setNullable(true);
64
65
            return;
66
        }
67
68
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
0 ignored issues
show
Bug introduced by
It seems like $docComment can also be of type true; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', /** @scrutinizer ignore-type */ $docComment, $matches);
Loading history...
69
70
        if (! count($matches)) {
71
            $this->setNullable(true);
72
73
            return;
74
        }
75
76
        $varDocComment = end($matches);
77
78
        $this->types = explode('|', $varDocComment);
79
        $this->arrayTypes = str_replace('[]', '', $this->types);
80
81
        if (in_array('immutable', $this->types) || in_array('Immutable', $this->types)) {
82
            $this->setImmutable(true);
83
            unset($this->types['immutable'], $this->types['Immutable']);
84
85
            if (empty($this->types)) {
86
                return;
87
            }
88
        }
89
90
        $this->hasTypeDeclaration = true;
91
92
        $this->setNullable(strpos($varDocComment, 'null') !== false);
93
    }
94
95
    protected function isValidType($value): bool
96
    {
97
        if (! $this->hasTypeDeclaration) {
98
            return true;
99
        }
100
101
        if ($this->nullable() && $value === null) {
102
            return true;
103
        }
104
105
        foreach ($this->types as $currentType) {
106
            $isValidType = $this->assertTypeEquals($currentType, $value);
107
108
            if ($isValidType) {
109
                return true;
110
            }
111
        }
112
113
        return false;
114
    }
115
116
    protected function cast($value)
117
    {
118
        $castTo = null;
119
120
        foreach ($this->types as $type) {
121
            if (! is_subclass_of($type, DtoContract::class)) {
122
                continue;
123
            }
124
125
            $castTo = $type;
126
127
            break;
128
        }
129
130
        if (! $castTo) {
131
            return $value;
132
        }
133
134
        return new $castTo($value);
135
    }
136
137
    protected function castCollection(array $values)
138
    {
139
        $castTo = null;
140
141
        foreach ($this->arrayTypes as $type) {
142
            if (! is_subclass_of($type, DtoContract::class)) {
143
                continue;
144
            }
145
146
            $castTo = $type;
147
148
            break;
149
        }
150
151
        if (! $castTo) {
152
            return $values;
153
        }
154
155
        $casts = [];
156
157
        foreach ($values as $value) {
158
            $casts[] = new $castTo($value);
159
        }
160
161
        return $casts;
162
    }
163
164
    protected function shouldBeCastToCollection(array $values): bool
165
    {
166
        if (empty($values)) {
167
            return false;
168
        }
169
170
        foreach ($values as $key => $value) {
171
            if (is_string($key)) {
172
                return false;
173
            }
174
175
            if (! is_array($value)) {
176
                return false;
177
            }
178
        }
179
180
        return true;
181
    }
182
183
    protected function assertTypeEquals(string $type, $value): bool
184
    {
185
        if (strpos($type, '[]') !== false) {
186
            return $this->isValidGenericCollection($type, $value);
187
        }
188
189
        if ($type === 'mixed' && $value !== null) {
190
            return true;
191
        }
192
193
        return $value instanceof $type
194
            || gettype($value) === (self::TYPE_MAPPING[$type] ?? $type);
195
    }
196
197
    protected function isValidGenericCollection(string $type, $collection): bool
198
    {
199
        if (! is_array($collection)) {
200
            return false;
201
        }
202
203
        $valueType = str_replace('[]', '', $type);
204
205
        foreach ($collection as $value) {
206
            if (! $this->assertTypeEquals($valueType, $value)) {
207
                return false;
208
            }
209
        }
210
211
        return true;
212
    }
213
214
    public function set($value) :void
215
    {
216
        if (is_array($value)) {
217
            $value = $this->shouldBeCastToCollection($value) ? $this->castCollection($value) : $this->cast($value);
218
        }
219
220
        if (! $this->isValidType($value)) {
221
            throw new InvalidTypeDtoException($this, $value);
222
        }
223
224
        $this->setInitialized(true);
225
226
        $this->value = $value;
227
    }
228
229
    public function setInitialized(bool $bool):void
230
    {
231
        $this->initialised = $bool;
232
    }
233
234
    public function isInitialized() :bool
235
    {
236
        return $this->initialised;
237
    }
238
239
    public function getTypes(): array
240
    {
241
        return $this->types;
242
    }
243
244
    public function getFqn(): string
245
    {
246
        return "{$this->reflection->getDeclaringClass()->getName()}::{$this->reflection->getName()}";
247
    }
248
249
    public function nullable(): bool
250
    {
251
        return $this->nullable;
252
    }
253
254
    public function setNullable(bool $bool): void
255
    {
256
        $this->nullable = $bool;
257
    }
258
259
    public function immutable(): bool
260
    {
261
        return $this->immutable;
262
    }
263
264
    public function setImmutable(bool $immutable): void
265
    {
266
        $this->immutable = $immutable;
267
    }
268
269
    public function getDefault()
270
    {
271
        return $this->default;
272
    }
273
274
    public function setDefault($default): void
275
    {
276
        $this->default = $default;
277
    }
278
279
    public function isVisible(): bool
280
    {
281
        return $this->visible;
282
    }
283
284
    public function setVisible(bool $bool): bool
285
    {
286
        return $this->visible = $bool;
287
    }
288
289
    public function getValue()
290
    {
291
        if (! $this->nullable() && $this->value == null) {
292
            return $this->getDefault();
293
        }
294
295
        return $this->value;
296
    }
297
298
    public function getValueFromReflection($object)
299
    {
300
        return $this->reflection->getValue($object);
301
    }
302
303
    public function getName() :string
304
    {
305
        return $this->reflection->getName();
306
    }
307
}
308