Completed
Push — master ( 66ac21...449db5 )
by Lars
01:50
created

Property::isValidType()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6.0359

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 1
dl 0
loc 20
ccs 9
cts 10
cp 0.9
crap 6.0359
rs 8.9777
c 0
b 0
f 0
1
<?php
2
3
namespace Arrayy;
4
5
/**
6
 * Class Property
7
 *
8
 * inspired by https://github.com/spatie/value-object
9
 */
10
class Property extends \ReflectionProperty
11
{
12
    /**
13
     * @var array
14
     */
15
    protected static $typeMapping = [
16
        'int'   => 'integer',
17
        'bool'  => 'boolean',
18
        'float' => 'double',
19
    ];
20
21
    /**
22
     * @var bool
23
     */
24
    protected $hasTypeDeclaration = false;
25
26
    /**
27
     * @var bool
28
     */
29
    protected $isNullable = false;
30
31
    /**
32
     * @var array
33
     */
34
    protected $types = [];
35
36
    /**
37
     * Property constructor.
38
     *
39
     * @param \ReflectionProperty $reflectionProperty
40
     * @param object              $fakeObject
41
     *
42
     * @throws \ReflectionException
43
     */
44 2
    public function __construct($reflectionProperty, $fakeObject = null)
45
    {
46 2
        parent::__construct($fakeObject, $reflectionProperty->getName());
47 2
    }
48
49
    /**
50
     * @param mixed $value
51
     *
52
     * @return bool
53
     */
54 8
    public function checkType($value): bool
55
    {
56 8
        if (!$this->hasTypeDeclaration) {
57
            return true;
58
        }
59
60 8
        if ($this->isNullable && $value === null) {
61 5
            return true;
62
        }
63
64 8
        foreach ($this->types as $currentType) {
65 8
            $isValidType = $this->assertTypeEquals($currentType, $value);
66
67 8
            if ($isValidType) {
68 8
                return true;
69
            }
70
        }
71
72 4
        $type = \gettype($value);
73
74 4
        $expectedTypes = \implode('|', $this->getTypes());
75
76 4
        throw new \InvalidArgumentException("Invalid type: expected {$this->name} to be of type {{$expectedTypes}}, instead got value `" . \print_r($value, true) . "` with type {{$type}}.");
77
    }
78
79 2
    public static function fromPhpDocumentorProperty(\phpDocumentor\Reflection\DocBlock\Tags\Property $phpDocumentorReflectionProperty): self
80
    {
81 2
        $tmpProperty = $phpDocumentorReflectionProperty->getVariableName();
82 2
        $tmpObject = new \stdClass();
83 2
        $tmpObject->{$tmpProperty} = null;
84
85 2
        $tmpReflection = new self(new \ReflectionProperty($tmpObject, $tmpProperty), $tmpObject);
86
87 2
        $type = $phpDocumentorReflectionProperty->getType();
88
89 2
        if ($type) {
90 2
            $tmpReflection->hasTypeDeclaration = true;
91
92 2
            $docTypes = self::parseDocTypeObject($type);
93 2
            if (\is_array($docTypes) === true) {
94 2
                foreach ($docTypes as $docType) {
95 2
                    $tmpReflection->types[] = $docType;
96
                }
97
            } else {
98 2
                $tmpReflection->types[] = $docTypes;
99
            }
100
101 2
            if (\in_array('null', $tmpReflection->types, true)) {
102 2
                $tmpReflection->isNullable = true;
103
            }
104
        }
105
106 2
        return $tmpReflection;
107
    }
108
109 4
    public function getTypes(): array
110
    {
111 4
        return $this->types;
112
    }
113
114
    /**
115
     * @param \phpDocumentor\Reflection\Type $type
116
     *
117
     * @return string|string[]
118
     */
119 2
    public static function parseDocTypeObject($type)
120
    {
121 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Object_) {
122 1
            $tmpObject = (string) $type->getFqsen();
123 1
            if ($tmpObject) {
124 1
                return $tmpObject;
125
            }
126
127
            return 'object';
128
        }
129
130 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Compound) {
131 2
            $types = [];
132 2
            foreach ($type as $subType) {
133 2
                $types[] = self::parseDocTypeObject($subType);
134
            }
135
136 2
            return $types;
137
        }
138
139 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Array_) {
140 1
            $valueTypeTmp = $type->getValueType() . '';
141 1
            if ($valueTypeTmp !== 'mixed') {
142 1
                return $valueTypeTmp . '[]';
143
            }
144
145
            return 'array';
146
        }
147
148 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Null_) {
149 2
            return 'null';
150
        }
151
152 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Mixed_) {
153
            return 'mixed';
154
        }
155
156 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Scalar) {
157
            return 'string|int|float|bool';
158
        }
159
160 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Boolean) {
161
            return 'bool';
162
        }
163
164 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Callable_) {
165
            return 'callable';
166
        }
167
168 2
        if ($type instanceof \phpDocumentor\Reflection\Types\Float_) {
169
            return 'float';
170
        }
171
172 2
        if ($type instanceof \phpDocumentor\Reflection\Types\String_) {
173 2
            return 'string';
174
        }
175
176 1
        if ($type instanceof \phpDocumentor\Reflection\Types\Integer) {
177 1
            return 'int';
178
        }
179
180
        if ($type instanceof \phpDocumentor\Reflection\Types\Void_) {
181
            return 'void';
182
        }
183
184
        if ($type instanceof \phpDocumentor\Reflection\Types\Resource_) {
185
            return 'resource';
186
        }
187
188
        return $type . '';
189
    }
190
191
    /**
192
     * @param string $type
193
     * @param mixed  $value
194
     *
195
     * @return bool
196
     */
197 8
    protected function assertTypeEquals(string $type, $value): bool
198
    {
199 8
        if (\strpos($type, '[]') !== false) {
200 5
            return $this->isValidGenericCollection($type, $value);
201
        }
202
203 8
        if ($type === 'mixed' && $value !== null) {
204
            return true;
205
        }
206
207 8
        return $value instanceof $type
208
               ||
209 8
               \gettype($value) === (self::$typeMapping[$type] ?? $type);
210
    }
211
212 5
    protected function isValidGenericCollection(string $type, $collection): bool
213
    {
214 5
        if (!\is_array($collection)) {
215 1
            return false;
216
        }
217
218 4
        $valueType = \str_replace('[]', '', $type);
219
220 4
        foreach ($collection as $value) {
221 4
            if (!$this->assertTypeEquals($valueType, $value)) {
222 4
                return false;
223
            }
224
        }
225
226 3
        return true;
227
    }
228
}
229