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.
Completed
Pull Request — master (#55)
by Arthur
02:21 queued 55s
created

Property   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 1

Importance

Changes 0
Metric Value
wmc 55
lcom 3
cbo 1
dl 0
loc 317
rs 6
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A fromReflection() 0 4 1
A __construct() 0 8 1
A set() 0 14 4
A setUninitialized() 0 4 1
A getTypes() 0 4 1
A getFqn() 0 4 1
A isNullable() 0 4 1
A isRequired() 0 4 1
A setRequired() 0 4 1
A setNullable() 0 4 1
A isOptional() 0 4 1
A resolveTypeDefinition() 0 27 3
B isValidType() 0 20 6
A cast() 0 20 4
A castCollection() 0 26 5
A shouldBeCastToCollection() 0 18 5
A assertTypeEquals() 0 13 5
A isValidGenericCollection() 0 16 4
A getRules() 0 4 1
A getDefault() 0 4 1
A setDefault() 0 4 1
A addRule() 0 8 2
A getActualValue() 0 8 3
A __call() 0 4 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. 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 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
9
class Property
10
{
11
    /** @var array */
12
    protected static $typeMapping = [
13
        'int' => 'integer',
14
        'bool' => 'boolean',
15
        'float' => 'double',
16
    ];
17
18
    /** @var \Spatie\DataTransferObject\DataTransferObject */
19
    protected $valueObject;
20
21
    /** @var bool */
22
    protected $hasTypeDeclaration = false;
23
24
    /** @var bool */
25
    protected $isNullable = false;
26
27
    /** @var bool */
28
    protected $isRequired = true;
29
30
    /** @var bool */
31
    protected $isInitialised = false;
32
33
    /** @var array */
34
    protected $types = [];
35
36
    /** @var array */
37
    protected $arrayTypes = [];
38
39
    /** @var string */
40
    protected $rules;
41
42
    /** @var mixed */
43
    protected $default;
44
45
    /** @var mixed */
46
    protected $actualValue;
47
48
    /** @var ReflectionProperty */
49
    protected $reflection;
50
51
    public static function fromReflection(DataTransferObject $valueObject, ReflectionProperty $reflectionProperty)
52
    {
53
        return new self($valueObject, $reflectionProperty);
54
    }
55
56
    public function __construct(DataTransferObject $valueObject, ReflectionProperty $reflectionProperty)
57
    {
58
        $this->reflection = $reflectionProperty;
59
60
        $this->valueObject = $valueObject;
61
62
        $this->resolveTypeDefinition();
63
    }
64
65
    public function set($value)
66
    {
67
        if (is_array($value)) {
68
            $value = $this->shouldBeCastToCollection($value) ? $this->castCollection($value) : $this->cast($value);
69
        }
70
71
        if (! $this->isValidType($value)) {
72
            throw DataTransferObjectError::invalidType($this, $value);
73
        }
74
75
        $this->isInitialised = true;
76
77
        $this->actualValue = $value;
78
    }
79
80
    public function setUninitialized()
81
    {
82
        $this->isInitialised = false;
83
    }
84
85
    public function getTypes(): array
86
    {
87
        return $this->types;
88
    }
89
90
    public function getFqn(): string
91
    {
92
        return "{$this->reflection->getDeclaringClass()->getName()}::{$this->reflection->getName()}";
93
    }
94
95
    public function isNullable(): bool
96
    {
97
        return $this->isNullable;
98
    }
99
100
    /**
101
     * @return bool
102
     */
103
    public function isRequired(): bool
104
    {
105
        return $this->isRequired;
106
    }
107
108
    public function setRequired(bool $bool): void
109
    {
110
        $this->isRequired = $bool;
111
    }
112
113
    public function setNullable(bool $bool): void
114
    {
115
        $this->isNullable = $bool;
116
    }
117
118
    /**
119
     * @return bool
120
     */
121
    public function isOptional(): bool
122
    {
123
        return ! $this->isRequired;
124
    }
125
126
    protected function resolveTypeDefinition()
127
    {
128
        $docComment = $this->reflection->getDocComment();
129
130
        if (! $docComment) {
131
            $this->isNullable = true;
132
133
            return;
134
        }
135
136
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
137
138
        if (! count($matches)) {
139
            $this->isNullable = true;
140
141
            return;
142
        }
143
144
        $this->hasTypeDeclaration = true;
145
146
        $varDocComment = end($matches);
147
148
        $this->types = explode('|', $varDocComment);
149
        $this->arrayTypes = str_replace('[]', '', $this->types);
150
151
        $this->isNullable = strpos($varDocComment, 'null') !== false;
152
    }
153
154
    protected function isValidType($value): bool
155
    {
156
        if (! $this->hasTypeDeclaration) {
157
            return true;
158
        }
159
160
        if ($this->isNullable && $value === null) {
161
            return true;
162
        }
163
164
        foreach ($this->types as $currentType) {
165
            $isValidType = $this->assertTypeEquals($currentType, $value);
166
167
            if ($isValidType) {
168
                return true;
169
            }
170
        }
171
172
        return false;
173
    }
174
175
    protected function cast($value)
176
    {
177
        $castTo = null;
178
179
        foreach ($this->types as $type) {
180
            if (! is_subclass_of($type, DataTransferObject::class)) {
181
                continue;
182
            }
183
184
            $castTo = $type;
185
186
            break;
187
        }
188
189
        if (! $castTo) {
190
            return $value;
191
        }
192
193
        return new $castTo($value);
194
    }
195
196
    protected function castCollection(array $values)
197
    {
198
        $castTo = null;
199
200
        foreach ($this->arrayTypes as $type) {
201
            if (! is_subclass_of($type, DataTransferObject::class)) {
202
                continue;
203
            }
204
205
            $castTo = $type;
206
207
            break;
208
        }
209
210
        if (! $castTo) {
211
            return $values;
212
        }
213
214
        $casts = [];
215
216
        foreach ($values as $value) {
217
            $casts[] = new $castTo($value);
218
        }
219
220
        return $casts;
221
    }
222
223
    protected function shouldBeCastToCollection(array $values): bool
224
    {
225
        if (empty($values)) {
226
            return false;
227
        }
228
229
        foreach ($values as $key => $value) {
230
            if (is_string($key)) {
231
                return false;
232
            }
233
234
            if (! is_array($value)) {
235
                return false;
236
            }
237
        }
238
239
        return true;
240
    }
241
242
    protected function assertTypeEquals(string $type, $value): bool
243
    {
244
        if (strpos($type, '[]') !== false) {
245
            return $this->isValidGenericCollection($type, $value);
246
        }
247
248
        if ($type === 'mixed' && $value !== null) {
249
            return true;
250
        }
251
252
        return $value instanceof $type
253
            || gettype($value) === (self::$typeMapping[$type] ?? $type);
254
    }
255
256
    protected function isValidGenericCollection(string $type, $collection): bool
257
    {
258
        if (! is_array($collection)) {
259
            return false;
260
        }
261
262
        $valueType = str_replace('[]', '', $type);
263
264
        foreach ($collection as $value) {
265
            if (! $this->assertTypeEquals($valueType, $value)) {
266
                return false;
267
            }
268
        }
269
270
        return true;
271
    }
272
273
    /**
274
     * @return string
275
     */
276
    public function getRules(): string
277
    {
278
        return $this->rules;
279
    }
280
281
    /**
282
     * @return mixed
283
     */
284
    public function getDefault()
285
    {
286
        return $this->default;
287
    }
288
289
    /**
290
     * @param mixed $default
291
     */
292
    public function setDefault($default): void
293
    {
294
        $this->default = $default;
295
    }
296
297
    /**
298
     * @param string $rules
299
     */
300
    public function addRule(string $rules): void
301
    {
302
        if (! isset($this->rules)) {
303
            $this->rules = $rules;
304
        } else {
305
            $this->rules = $this->rules.'|'.$rules;
306
        }
307
    }
308
309
    /**
310
     * @return mixed
311
     */
312
    public function getActualValue()
313
    {
314
        if (! $this->isNullable && $this->actualValue == null) {
315
            return $this->getDefault();
316
        }
317
318
        return $this->actualValue;
319
    }
320
321
    public function __call($name, $arguments)
322
    {
323
        return call_user_func_array([$this->reflection, $name], $arguments);
324
    }
325
}
326