1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Equip\Data\Traits; |
4
|
|
|
|
5
|
|
|
use Equip\Data\ArraySerializableInterface; |
6
|
|
|
use RuntimeException; |
7
|
|
|
|
8
|
|
|
trait ImmutableValueObjectTrait |
9
|
|
|
{ |
10
|
|
|
use ProtectedValueObjectTrait; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Hydrate the object with new values |
14
|
|
|
* |
15
|
|
|
* @param array $data |
16
|
|
|
*/ |
17
|
16 |
|
public function __construct(array $data = []) |
18
|
|
|
{ |
19
|
16 |
|
if ($data) { |
|
|
|
|
20
|
14 |
|
$this->apply($data); |
21
|
12 |
|
} |
22
|
14 |
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Create a copy of the object with new values |
26
|
|
|
* |
27
|
|
|
* @param array $data |
28
|
|
|
* |
29
|
|
|
* @return static |
30
|
|
|
*/ |
31
|
2 |
|
public function withData(array $data) |
32
|
|
|
{ |
33
|
2 |
|
$copy = clone $this; |
34
|
2 |
|
$copy->apply($data); |
35
|
2 |
|
return $copy; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Type definitions for object properties |
40
|
|
|
* |
41
|
|
|
* return [ |
42
|
|
|
* 'id' => 'int', |
43
|
|
|
* 'email' => 'string', |
44
|
|
|
* 'is_deleted' => 'bool', |
45
|
|
|
* ]; |
46
|
|
|
* |
47
|
|
|
* Overload this method to enable type coercion! |
48
|
|
|
* |
49
|
|
|
* @return array |
50
|
|
|
*/ |
51
|
3 |
|
private function types() |
52
|
|
|
{ |
53
|
3 |
|
return []; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Expected types for object properties |
58
|
|
|
* |
59
|
|
|
* return [ |
60
|
|
|
* 'user' => User::class, |
61
|
|
|
* ]; |
62
|
|
|
* |
63
|
|
|
* @return array |
64
|
|
|
*/ |
65
|
7 |
|
private function expects() |
66
|
|
|
{ |
67
|
7 |
|
return []; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Validates the current object |
72
|
|
|
* |
73
|
|
|
* @return void |
74
|
|
|
* |
75
|
|
|
* @throws \DomainException If the object is not valid |
76
|
|
|
*/ |
77
|
8 |
|
private function validate() |
78
|
|
|
{ |
79
|
8 |
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Update the current object with new values |
83
|
|
|
* |
84
|
|
|
* NOTE: Be careful not to violate immutability when using this method! |
85
|
|
|
* |
86
|
|
|
* @uses types() |
87
|
|
|
* @uses expects() |
88
|
|
|
* @uses validate() |
89
|
|
|
* |
90
|
|
|
* @param array $data |
91
|
|
|
* |
92
|
|
|
* @return void |
93
|
|
|
* |
94
|
|
|
* @throws \DomainException If a data value is not of the expected type |
95
|
|
|
*/ |
96
|
14 |
|
private function apply(array $data) |
97
|
|
|
{ |
98
|
|
|
// Discard any values that do not exist in this object |
99
|
14 |
|
$data = array_intersect_key($data, get_object_vars($this)); |
100
|
|
|
|
101
|
|
|
// Type coercion and class expectations are not run on null values |
102
|
|
|
$values = array_filter($data, static function ($value) { |
103
|
14 |
|
return null !== $value; |
104
|
14 |
|
}); |
105
|
|
|
|
106
|
14 |
|
$types = array_intersect_key($this->types(), $values); |
107
|
14 |
|
$expects = array_intersect_key($this->expects(), $values); |
108
|
|
|
|
109
|
14 |
|
foreach ($types as $key => $type) { |
110
|
12 |
|
settype($data[$key], $type); |
111
|
14 |
|
} |
112
|
|
|
|
113
|
14 |
|
foreach ($expects as $key => $class) { |
114
|
3 |
|
if (false === $data[$key] instanceof $class) { |
115
|
1 |
|
throw new \DomainException(sprintf( |
116
|
1 |
|
'Expected value of `%s` to be an object of `%s` type', |
117
|
1 |
|
$key, |
118
|
|
|
$class |
119
|
1 |
|
)); |
120
|
|
|
} |
121
|
13 |
|
} |
122
|
|
|
|
123
|
13 |
|
foreach ($data as $key => $value) { |
124
|
13 |
|
$this->$key = $value; |
125
|
13 |
|
} |
126
|
|
|
|
127
|
13 |
|
$this->validate(); |
|
|
|
|
128
|
12 |
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Check if the current object has a property |
132
|
|
|
* |
133
|
|
|
* @param string $key |
134
|
|
|
* |
135
|
|
|
* @return boolean |
136
|
|
|
*/ |
137
|
2 |
|
public function has($key) |
138
|
|
|
{ |
139
|
2 |
|
return property_exists($this, $key); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Get the current object values as an array |
144
|
|
|
* |
145
|
|
|
* @return array |
146
|
|
|
*/ |
147
|
|
|
public function toArray() |
148
|
|
|
{ |
149
|
8 |
|
return array_map(static function ($value) { |
150
|
8 |
|
if ($value instanceof ArraySerializableInterface) { |
151
|
1 |
|
$value = $value->toArray(); |
152
|
1 |
|
} |
153
|
8 |
|
return $value; |
154
|
8 |
|
}, get_object_vars($this)); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.