Completed
Pull Request — master (#21)
by
unknown
09:15
created

Property::isNullable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 = true;
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
    public function isNullable(): bool
66
    {
67
        return $this->isNullable;
68
    }
69
70
    protected function resolveTypeDefinition()
71
    {
72
        $docComment = $this->getDocComment();
73
74
        if (! $docComment) {
75
            return;
76
        }
77
78
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
79
80
        if (! count($matches)) {
81
            return;
82
        }
83
84
        $this->hasTypeDeclaration = true;
85
86
        $varDocComment = end($matches);
87
88
        $this->types = explode('|', $varDocComment);
89
90
        $this->isNullable = strpos($varDocComment, 'null') !== false;
91
    }
92
93
    protected function isValidType($value): bool
94
    {
95
        if (! $this->hasTypeDeclaration) {
96
            return true;
97
        }
98
99
        if ($this->isNullable && $value === null) {
100
            return true;
101
        }
102
103
        foreach ($this->types as $currentType) {
104
            $isValidType = $this->assertTypeEquals($currentType, $value);
105
106
            if ($isValidType) {
107
                return true;
108
            }
109
        }
110
111
        return false;
112
    }
113
114
    protected function assertTypeEquals(string $type, $value): bool
115
    {
116
        if (strpos($type, '[]') !== false) {
117
            return $this->isValidGenericCollection($type, $value);
118
        }
119
120
        if ($type === 'mixed' && $value !== null) {
121
            return true;
122
        }
123
124
        return $value instanceof $type
125
            || gettype($value) === (self::$typeMapping[$type] ?? $type);
126
    }
127
128
    protected function isValidGenericCollection(string $type, $collection): bool
129
    {
130
        if (! is_array($collection)) {
131
            return false;
132
        }
133
134
        $valueType = str_replace('[]', '', $type);
135
136
        foreach ($collection as $value) {
137
            if (! $this->assertTypeEquals($valueType, $value)) {
138
                return false;
139
            }
140
        }
141
142
        return true;
143
    }
144
}
145