Completed
Pull Request — master (#14)
by Brent
17:31
created

ValueObjectProperty::getTypes()   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 ValueObjectProperty 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
    /** @var \Spatie\ValueObject\ValueObjectDefinition */
31
    protected $valueObjectDefinition;
32
33
    public static function fromReflection(
34
        ValueObject $valueObject,
35
        ValueObjectDefinition $valueObjectDefinition,
36
        ReflectionProperty $reflectionProperty
37
    ) {
38
        return new self($valueObject, $valueObjectDefinition, $reflectionProperty);
39
    }
40
41
    public function __construct(
42
        ValueObject $valueObject,
43
        ValueObjectDefinition $valueObjectDefinition,
44
        ReflectionProperty $reflectionProperty
45
    ) {
46
        parent::__construct($reflectionProperty->class, $reflectionProperty->getName());
47
48
        $this->valueObject = $valueObject;
49
50
        $this->valueObjectDefinition = $valueObjectDefinition;
51
52
        $this->resolveTypeDefinition();
53
    }
54
55
    public function set($value)
56
    {
57
        if (! $this->isValidType($value)) {
58
            throw ValueObjectError::invalidType($this, $value);
59
        }
60
61
        $this->isInitialised = true;
62
63
        $this->valueObject->{$this->getName()} = $value;
64
    }
65
66
    public function getTypes(): array
67
    {
68
        return $this->types;
69
    }
70
71
    public function getFqn(): string
72
    {
73
        return "{$this->getDeclaringClass()->getName()}::{$this->getName()}";
74
    }
75
76
    protected function resolveTypeDefinition()
77
    {
78
        $docComment = $this->getDocComment();
79
80
        if (! $docComment) {
81
            return;
82
        }
83
84
        preg_match('/\@var ([\w|\\\\]+)/', $docComment, $matches);
85
86
        if (! count($matches)) {
87
            return;
88
        }
89
90
        $varDocComment = end($matches);
91
92
        $this->types = explode('|', $varDocComment);
93
94
        $this->isNullable = strpos($varDocComment, 'null') !== false;
95
96
        $this->hasTypeDeclaration = true;
97
    }
98
99
    protected function isValidType($value): bool
100
    {
101
        if (! $this->hasTypeDeclaration) {
102
            return true;
103
        }
104
105
        if ($this->isNullable && $value === null) {
106
            return true;
107
        }
108
109
        foreach ($this->types as $currentType) {
110
            $isValidType = $this->assertTypeEquals($currentType, $value);
111
112
            if ($isValidType) {
113
                return true;
114
            }
115
        }
116
117
        return false;
118
    }
119
120
    protected function assertTypeEquals(string $type, $value): bool
121
    {
122
        if ($type === 'mixed' && $value !== null) {
123
            return true;
124
        }
125
126
        if (class_exists($type)) {
127
            return $value instanceof $type;
128
        }
129
130
        if ($this->valueObjectDefinition->hasAlias($type)) {
131
            $type = $this->valueObjectDefinition->resolveAlias($type);
132
133
            return $value instanceof $type;
134
        }
135
136
        return gettype($value) === (self::$typeMapping[$type] ?? $type);
137
    }
138
}
139