Completed
Push — master ( 3f57f7...bacaac )
by Brent
33:36 queued 02:38
created

Property::isValidGenericCollection()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 4
nop 2
1
<?php
2
3
namespace Spatie\ValueObject;
4
5
use ReflectionProperty;
6
7
class Property extends ReflectionProperty
8
{
9
    /** @var array */
10
    protected static $typeMapping = [
11
        'int' => 'integer',
12
        'bool' => 'boolean',
13
    ];
14
15
    /** @var \Spatie\ValueObject\ValueObject */
16
    protected $valueObject;
17
18
    /** @var bool */
19
    protected $hasTypeDeclaration = false;
20
21
    /** @var bool */
22
    protected $isNullable = false;
23
24
    /** @var bool */
25
    protected $isInitialised = false;
26
27
    /** @var array */
28
    protected $types = [];
29
30
    public static function fromReflection(ValueObject $valueObject, ReflectionProperty $reflectionProperty)
31
    {
32
        return new self($valueObject, $reflectionProperty);
33
    }
34
35
    public function __construct(ValueObject $valueObject, ReflectionProperty $reflectionProperty)
36
    {
37
        parent::__construct($reflectionProperty->class, $reflectionProperty->getName());
38
39
        $this->valueObject = $valueObject;
40
41
        $this->resolveTypeDefinition();
42
    }
43
44
    public function set($value)
45
    {
46
        if (! $this->isValidType($value)) {
47
            throw ValueObjectError::invalidType($this, $value);
48
        }
49
50
        $this->isInitialised = true;
51
52
        $this->valueObject->{$this->getName()} = $value;
53
    }
54
55
    public function getTypes(): array
56
    {
57
        return $this->types;
58
    }
59
60
    public function getFqn(): string
61
    {
62
        return "{$this->getDeclaringClass()->getName()}::{$this->getName()}";
63
    }
64
65
    protected function resolveTypeDefinition()
66
    {
67
        $docComment = $this->getDocComment();
68
69
        if (! $docComment) {
70
            return;
71
        }
72
73
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
74
75
        if (! count($matches)) {
76
            return;
77
        }
78
79
        $this->hasTypeDeclaration = true;
80
81
        $varDocComment = end($matches);
82
83
        $this->types = explode('|', $varDocComment);
84
85
        $this->isNullable = strpos($varDocComment, 'null') !== false;
86
    }
87
88
    protected function isValidType($value): bool
89
    {
90
        if (! $this->hasTypeDeclaration) {
91
            return true;
92
        }
93
94
        if ($this->isNullable && $value === null) {
95
            return true;
96
        }
97
98
        foreach ($this->types as $currentType) {
99
            $isValidType = $this->assertTypeEquals($currentType, $value);
100
101
            if ($isValidType) {
102
                return true;
103
            }
104
        }
105
106
        return false;
107
    }
108
109
    protected function assertTypeEquals(string $type, $value): bool
110
    {
111
        if (strpos($type, '[]') !== false) {
112
            return $this->isValidGenericCollection($type, $value);
113
        }
114
115
        if ($type === 'mixed' && $value !== null) {
116
            return true;
117
        }
118
119
        if (class_exists($type)) {
120
            return $value instanceof $type;
121
        }
122
123
        return gettype($value) === (self::$typeMapping[$type] ?? $type);
124
    }
125
126
    protected function isValidGenericCollection(string $type, $collection): bool
127
    {
128
        if (! is_array($collection)) {
129
            return false;
130
        }
131
132
        $valueType = str_replace('[]', '', $type);
133
134
        foreach ($collection as $value) {
135
            if (! $this->assertTypeEquals($valueType, $value)) {
136
                return false;
137
            }
138
        }
139
140
        return true;
141
    }
142
}
143