1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the ICanBoogie package. |
5
|
|
|
* |
6
|
|
|
* (c) Olivier Laviale <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace ICanBoogie\Accessor; |
13
|
|
|
|
14
|
|
|
use ICanBoogie\PropertyNotDefined; |
15
|
|
|
use ICanBoogie\PropertyNotReadable; |
16
|
|
|
use ICanBoogie\PropertyNotWritable; |
17
|
|
|
use ReflectionClass; |
18
|
|
|
use ReflectionException; |
19
|
|
|
use ReflectionObject; |
20
|
|
|
|
21
|
|
|
use function array_keys; |
22
|
|
|
use function get_class; |
23
|
|
|
use function get_object_vars; |
24
|
|
|
use function implode; |
25
|
|
|
use function method_exists; |
26
|
|
|
use function property_exists; |
27
|
|
|
use function sprintf; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Implements ICanBoogie's accessor pattern. |
31
|
|
|
*/ |
32
|
|
|
trait AccessorTrait |
33
|
|
|
{ |
34
|
|
|
use FormatAsSnake; |
35
|
|
|
|
36
|
|
|
public function __get($property) |
37
|
|
|
{ |
38
|
|
|
return $this->accessor_get($property); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @throws ReflectionException |
43
|
|
|
*/ |
44
|
|
|
public function __set($property, $value) |
45
|
|
|
{ |
46
|
|
|
$this->accessor_set($property, $value); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
public function has_property(string $property): bool |
50
|
|
|
{ |
51
|
|
|
return property_exists($this, $property) |
52
|
|
|
|| $this->has_method( |
53
|
|
|
static::accessor_format( |
54
|
|
|
$property, |
55
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER |
56
|
|
|
) |
57
|
|
|
) |
58
|
|
|
|| $this->has_method( |
59
|
|
|
static::accessor_format( |
60
|
|
|
$property, |
61
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER, |
62
|
|
|
HasAccessor::ACCESSOR_IS_LAZY |
63
|
|
|
) |
64
|
|
|
) |
65
|
|
|
|| $this->has_method( |
66
|
|
|
static::accessor_format( |
67
|
|
|
$property, |
68
|
|
|
HasAccessor::ACCESSOR_TYPE_SETTER |
69
|
|
|
) |
70
|
|
|
) |
71
|
|
|
|| $this->has_method( |
72
|
|
|
static::accessor_format( |
73
|
|
|
$property, |
74
|
|
|
HasAccessor::ACCESSOR_TYPE_SETTER, |
75
|
|
|
HasAccessor::ACCESSOR_IS_LAZY |
76
|
|
|
) |
77
|
|
|
); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
public function has_method(string $method): bool |
81
|
|
|
{ |
82
|
|
|
return method_exists($this, $method); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Returns the value of an inaccessible property. |
87
|
|
|
* |
88
|
|
|
* The method tries to get the property using the getter and lazy getter methods. |
89
|
|
|
* |
90
|
|
|
* @return mixed |
91
|
|
|
*/ |
92
|
|
|
private function accessor_get(string $property) |
93
|
|
|
{ |
94
|
|
|
$method = static::accessor_format( |
95
|
|
|
$property, |
96
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER |
97
|
|
|
); |
98
|
|
|
|
99
|
|
|
if ($this->has_method($method)) { |
100
|
|
|
return $this->$method(); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$method = static::accessor_format( |
104
|
|
|
$property, |
105
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER, |
106
|
|
|
HasAccessor::ACCESSOR_IS_LAZY |
107
|
|
|
); |
108
|
|
|
|
109
|
|
|
if ($this->has_method($method)) { |
110
|
|
|
return $this->$property = $this->$method(); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$this->assert_property_is_readable($property); |
114
|
|
|
} //@codeCoverageIgnore |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Sets the value of an inaccessible property. |
118
|
|
|
* |
119
|
|
|
* The method is called because the property does not exist, its visibility is |
120
|
|
|
* _protected_ or _private_, or because although it is visible it was unset and is no |
121
|
|
|
* longer accessible. |
122
|
|
|
* |
123
|
|
|
* A `set_<property>` method can be used the handle virtual properties, for instance a |
124
|
|
|
* `minute` property that would alter a `second` property. |
125
|
|
|
* |
126
|
|
|
* A `lazy_set_<property>` method can be used to set properties that are protected or |
127
|
|
|
* private, which can be used to make properties write-only for example. |
128
|
|
|
* |
129
|
|
|
* @param mixed $value |
130
|
|
|
* |
131
|
|
|
* @throws ReflectionException |
132
|
|
|
*/ |
133
|
|
|
private function accessor_set(string $property, $value): void |
134
|
|
|
{ |
135
|
|
|
$method = static::accessor_format( |
136
|
|
|
$property, |
137
|
|
|
HasAccessor::ACCESSOR_TYPE_SETTER |
138
|
|
|
); |
139
|
|
|
|
140
|
|
|
if ($this->has_method($method)) { |
141
|
|
|
$this->$method($value); |
142
|
|
|
|
143
|
|
|
return; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
$method = static::accessor_format( |
147
|
|
|
$property, |
148
|
|
|
HasAccessor::ACCESSOR_TYPE_SETTER, |
149
|
|
|
HasAccessor::ACCESSOR_IS_LAZY |
150
|
|
|
); |
151
|
|
|
|
152
|
|
|
if ($this->has_method($method)) { |
153
|
|
|
$this->$property = $this->$method($value); |
154
|
|
|
|
155
|
|
|
return; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
$this->assert_property_is_writable($property); |
159
|
|
|
|
160
|
|
|
$this->$property = $value; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Asserts that a property is readable. |
165
|
|
|
* |
166
|
|
|
* @throws PropertyNotDefined when the property is not defined. |
167
|
|
|
* @throws PropertyNotReadable when the property is not accessible or is write-only |
168
|
|
|
* (the property is not defined and only a setter is available). |
169
|
|
|
*/ |
170
|
|
|
private function assert_property_is_readable(string $property): void |
171
|
|
|
{ |
172
|
|
|
try { |
173
|
|
|
$reflexion_class = new ReflectionClass($this); |
174
|
|
|
$reflexion_property = $reflexion_class->getProperty($property); |
175
|
|
|
|
176
|
|
|
if (!$reflexion_property->isPublic()) { |
177
|
|
|
throw new PropertyNotReadable([$property, $this]); |
178
|
|
|
} |
179
|
|
|
} catch (ReflectionException $e) { |
180
|
|
|
# |
181
|
|
|
# An exception may occur if the property is not defined, we don't care about that. |
182
|
|
|
# |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
$this->assert_no_accessor( |
186
|
|
|
$property, |
187
|
|
|
HasAccessor::ACCESSOR_TYPE_SETTER, |
188
|
|
|
PropertyNotReadable::class |
189
|
|
|
); |
190
|
|
|
|
191
|
|
|
$properties = array_keys(get_object_vars($this)); |
192
|
|
|
|
193
|
|
|
if ($properties) { |
|
|
|
|
194
|
|
|
throw new PropertyNotDefined( |
195
|
|
|
sprintf( |
196
|
|
|
'Unknown or inaccessible property "%s"' |
197
|
|
|
. ' for object of class "%s" (available properties: %s).', |
198
|
|
|
$property, |
199
|
|
|
get_class($this), |
200
|
|
|
implode(', ', $properties) |
201
|
|
|
) |
202
|
|
|
); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
throw new PropertyNotDefined([$property, $this]); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Asserts that a property is writable. |
210
|
|
|
* |
211
|
|
|
* @throws PropertyNotWritable|ReflectionException when the property doesn't exist, has no lazy getter and is |
212
|
|
|
* not public; or when only a getter is implemented. |
213
|
|
|
*/ |
214
|
|
|
private function assert_property_is_writable(string $property): void |
215
|
|
|
{ |
216
|
|
|
if ( |
217
|
|
|
property_exists($this, $property) && |
218
|
|
|
!$this->has_method( |
219
|
|
|
static::accessor_format( |
220
|
|
|
$property, |
221
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER, |
222
|
|
|
HasAccessor::ACCESSOR_IS_LAZY |
223
|
|
|
) |
224
|
|
|
) |
225
|
|
|
) { |
226
|
|
|
$reflection = new ReflectionObject($this); |
227
|
|
|
$property_reflection = $reflection->getProperty($property); |
228
|
|
|
|
229
|
|
|
if (!$property_reflection->isPublic()) { |
230
|
|
|
throw new PropertyNotWritable([$property, $this]); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
$this->assert_no_accessor( |
237
|
|
|
$property, |
238
|
|
|
HasAccessor::ACCESSOR_TYPE_GETTER, |
239
|
|
|
PropertyNotWritable::class |
240
|
|
|
); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Asserts that an accessor is not implemented. |
245
|
|
|
* |
246
|
|
|
* @param string $type One of {@link HasAccessor::ACCESSOR_TYPE_GETTER} |
247
|
|
|
* and {@link HasAccessor::ACCESSOR_TYPE_SETTER}. |
248
|
|
|
*/ |
249
|
|
|
private function assert_no_accessor(string $property, string $type, string $exception_class): void |
250
|
|
|
{ |
251
|
|
|
if ($this->has_method(static::accessor_format($property, $type))) { |
252
|
|
|
throw new $exception_class([$property, $this]); |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
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.