Property   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 1
dl 0
loc 142
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A fromReflection() 0 4 1
A __construct() 0 8 1
A set() 0 10 2
A getTypes() 0 4 1
A getFqn() 0 4 1
A isNullable() 0 4 1
A resolveTypeDefinition() 0 26 3
B isValidType() 0 20 6
A assertTypeEquals() 0 13 5
A isValidGenericCollection() 0 16 4
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
    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
            $this->isNullable = true;
76
77
            return;
78
        }
79
80
        preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
81
82
        if (! count($matches)) {
83
            $this->isNullable = true;
84
85
            return;
86
        }
87
88
        $this->hasTypeDeclaration = true;
89
90
        $varDocComment = end($matches);
91
92
        $this->types = explode('|', $varDocComment);
93
94
        $this->isNullable = strpos($varDocComment, 'null') !== false;
95
    }
96
97
    protected function isValidType($value): bool
98
    {
99
        if (! $this->hasTypeDeclaration) {
100
            return true;
101
        }
102
103
        if ($this->isNullable && $value === null) {
104
            return true;
105
        }
106
107
        foreach ($this->types as $currentType) {
108
            $isValidType = $this->assertTypeEquals($currentType, $value);
109
110
            if ($isValidType) {
111
                return true;
112
            }
113
        }
114
115
        return false;
116
    }
117
118
    protected function assertTypeEquals(string $type, $value): bool
119
    {
120
        if (strpos($type, '[]') !== false) {
121
            return $this->isValidGenericCollection($type, $value);
122
        }
123
124
        if ($type === 'mixed' && $value !== null) {
125
            return true;
126
        }
127
128
        return $value instanceof $type
129
            || gettype($value) === (self::$typeMapping[$type] ?? $type);
130
    }
131
132
    protected function isValidGenericCollection(string $type, $collection): bool
133
    {
134
        if (! is_array($collection)) {
135
            return false;
136
        }
137
138
        $valueType = str_replace('[]', '', $type);
139
140
        foreach ($collection as $value) {
141
            if (! $this->assertTypeEquals($valueType, $value)) {
142
                return false;
143
            }
144
        }
145
146
        return true;
147
    }
148
}
149