Completed
Push — master ( dc7d5f...f74214 )
by Brent
25:02 queued 08:18
created

Property::isValidType()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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