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 (#76)
by
unknown
01:05
created

Property::isValidIterable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spatie\DataTransferObject;
6
7
use ReflectionProperty;
8
9
class Property extends ReflectionProperty
10
{
11
    /**
12
     * - The key is the type of the propoerty
13
     * - The callable receives the value and may return the same or a different one
14
     * @var array<string,callable(mixed $value)>
0 ignored issues
show
Documentation introduced by
The doc-type array<string,callable(mixed could not be parsed: Expected ">" at position 5, but found "(". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
15
     */
16
    public static $make = [
17
    ];
18
19
    /** @var array */
20
    protected static $typeMapping = [
21
        'int' => 'integer',
22
        'bool' => 'boolean',
23
        'float' => 'double',
24
    ];
25
26
    /** @var \Spatie\DataTransferObject\DataTransferObject */
27
    protected $valueObject;
28
29
    /** @var bool */
30
    protected $hasTypeDeclaration = false;
31
32
    /** @var bool */
33
    protected $isNullable = false;
34
35
    /** @var bool */
36
    protected $isInitialised = false;
37
38
    /** @var array */
39
    protected $types = [];
40
41
    /** @var array */
42
    protected $arrayTypes = [];
43
44
    public static function fromReflection(DataTransferObject $valueObject, ReflectionProperty $reflectionProperty)
45
    {
46
        return new self($valueObject, $reflectionProperty);
47
    }
48
49
    public function __construct(DataTransferObject $valueObject, ReflectionProperty $reflectionProperty)
50
    {
51
        parent::__construct($reflectionProperty->class, $reflectionProperty->getName());
52
53
        $this->valueObject = $valueObject;
54
55
        $this->resolveTypeDefinition();
56
    }
57
58
    public function set($value)
59
    {
60
        if (is_array($value)) {
61
            $value = $this->shouldBeCastToCollection($value) ? $this->castCollection($value) : $this->cast($value);
62
        } else {
63
            $value = $this->makeValue($value);
64
        }
65
66
        if (! $this->isValidType($value)) {
67
            throw DataTransferObjectError::invalidType($this, $value);
68
        }
69
70
        $this->isInitialised = true;
71
72
        $this->valueObject->{$this->getName()} = $value;
73
    }
74
75
    public function getTypes(): array
76
    {
77
        return $this->types;
78
    }
79
80
    public function getFqn(): string
81
    {
82
        return "{$this->getDeclaringClass()->getName()}::{$this->getName()}";
83
    }
84
85
    public function isNullable(): bool
86
    {
87
        return $this->isNullable;
88
    }
89
90
    protected function resolveTypeDefinition()
91
    {
92
        $docComment = $this->getDocComment();
93
94
        if (! $docComment) {
95
            $this->isNullable = true;
96
97
            return;
98
        }
99
100
        preg_match('/\@var ((?:(?:[\w|\\\\<>])+(?:\[\])?)+)/', $docComment, $matches);
101
102
        if (! count($matches)) {
103
            $this->isNullable = true;
104
105
            return;
106
        }
107
108
        $this->hasTypeDeclaration = true;
109
110
        $varDocComment = end($matches);
111
112
        $this->types = explode('|', $varDocComment);
113
        $this->arrayTypes = str_replace('[]', '', $this->types);
114
115
        if (preg_match_all('/iterable<([^|]*)>/', $varDocComment, $matches)) {
116
            $this->arrayTypes = array_merge($this->arrayTypes, $matches[1]);
117
        }
118
119
        $this->isNullable = strpos($varDocComment, 'null') !== false;
120
    }
121
122
    protected function isValidType($value): bool
123
    {
124
        if (! $this->hasTypeDeclaration) {
125
            return true;
126
        }
127
128
        if ($this->isNullable && $value === null) {
129
            return true;
130
        }
131
132
        foreach ($this->types as $currentType) {
133
            $isValidType = $this->assertTypeEquals($currentType, $value);
134
135
            if ($isValidType) {
136
                return true;
137
            }
138
        }
139
140
        return false;
141
    }
142
143
    protected function cast($value)
144
    {
145
        $castTo = null;
146
147
        foreach ($this->types as $type) {
148
            if (! is_subclass_of($type, DataTransferObject::class)) {
149
                continue;
150
            }
151
152
            $castTo = $type;
153
154
            break;
155
        }
156
157
        if (! $castTo) {
158
            return $value;
159
        }
160
161
        return new $castTo($value);
162
    }
163
164
    protected function castCollection(array $values)
165
    {
166
        $castTo = null;
167
168
        foreach ($this->arrayTypes as $type) {
169
            if (! is_subclass_of($type, DataTransferObject::class)) {
170
                continue;
171
            }
172
173
            $castTo = $type;
174
175
            break;
176
        }
177
178
        if (! $castTo) {
179
            return $values;
180
        }
181
182
        $casts = [];
183
184
        foreach ($values as $value) {
185
            $casts[] = new $castTo($value);
186
        }
187
188
        return $casts;
189
    }
190
191
    protected function shouldBeCastToCollection(array $values): bool
192
    {
193
        if (empty($values)) {
194
            return false;
195
        }
196
197
        foreach ($values as $key => $value) {
198
            if (is_string($key)) {
199
                return false;
200
            }
201
202
            if (! is_array($value)) {
203
                return false;
204
            }
205
        }
206
207
        return true;
208
    }
209
210
    protected function assertTypeEquals(string $type, $value): bool
211
    {
212
        if (strpos($type, '[]') !== false) {
213
            return $this->isValidArray($type, $value);
214
        }
215
216
        if ($type === 'iterable' || strpos($type, 'iterable<') === 0) {
217
            return $this->isValidIterable($type, $value);
218
        }
219
220
        if ($type === 'mixed' && $value !== null) {
221
            return true;
222
        }
223
224
        return $value instanceof $type
225
            || gettype($value) === (self::$typeMapping[$type] ?? $type);
226
    }
227
228
    protected function isValidArray(string $type, $collection): bool
229
    {
230
        if (! is_array($collection)) {
231
            return false;
232
        }
233
234
        $valueType = str_replace('[]', '', $type);
235
236
        return $this->isValidGenericCollection($valueType, $collection);
237
    }
238
239
    protected function isValidIterable(string $type, $collection): bool
240
    {
241
        if (! is_iterable($collection)) {
242
            return false;
243
        }
244
245
        if (preg_match('/^iterable<(.*)>$/', $type, $matches)) {
246
            return $this->isValidGenericCollection($matches[1], $collection);
247
        }
248
249
        return true;
250
    }
251
252
    protected function isValidGenericCollection(string $type, $collection): bool
253
    {
254
        foreach ($collection as $value) {
255
            if (! $this->assertTypeEquals($type, $value)) {
256
                return false;
257
            }
258
        }
259
260
        return true;
261
    }
262
263
    protected function makeValue($value)
264
    {
265
        foreach ($this->types as $type) {
266
            $type = ltrim($type, '\\');
267
268
            if (!isset($this::$make[$type])) {
269
                continue;
270
            }
271
272
            return $this::$make[$type]($value);
273
        }
274
275
        return $value;
276
    }
277
}
278