1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Base daft objects. |
4
|
|
|
* |
5
|
|
|
* @author SignpostMarv |
6
|
|
|
*/ |
7
|
|
|
declare(strict_types=1); |
8
|
|
|
|
9
|
|
|
namespace SignpostMarv\DaftObject; |
10
|
|
|
|
11
|
|
|
use ReflectionClass; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Base daft object. |
15
|
|
|
*/ |
16
|
|
|
abstract class AbstractDaftObject implements DaftObject |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* List of properties that can be defined on an implementation. |
20
|
|
|
* |
21
|
|
|
* @var string[] |
22
|
|
|
*/ |
23
|
|
|
const PROPERTIES = []; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* List of nullable properties that can be defined on an implementation. |
27
|
|
|
* |
28
|
|
|
* @var string[] |
29
|
|
|
*/ |
30
|
|
|
const NULLABLE_PROPERTIES = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Index of checked types. |
34
|
|
|
* |
35
|
|
|
* @see self::CheckTypeDefinesOwnIdProperties() |
36
|
|
|
* |
37
|
|
|
* @var bool[] |
38
|
|
|
*/ |
39
|
|
|
private static $checkedTypes = []; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Does some sanity checking. |
43
|
|
|
* |
44
|
|
|
* @see DefinesOwnIdPropertiesInterface |
45
|
|
|
* @see self::CheckTypeDefinesOwnIdProperties() |
46
|
|
|
* |
47
|
|
|
* @throws AlreadyIncorrectlyImplementedTypeException if static::class was previously determined to be incorrectly implemented |
48
|
|
|
*/ |
49
|
44 |
|
public function __construct() |
50
|
|
|
{ |
51
|
|
|
if ( |
52
|
44 |
|
($this instanceof DefinesOwnIdPropertiesInterface) && |
53
|
32 |
|
false === self::CheckTypeDefinesOwnIdProperties($this) |
54
|
|
|
) { |
55
|
1 |
|
throw new AlreadyIncorrectlyImplementedTypeException( |
56
|
1 |
|
get_class($this) // phpunit coverage does not pick up static::class here |
57
|
|
|
); |
58
|
|
|
} |
59
|
41 |
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* {@inheritdoc} |
63
|
|
|
*/ |
64
|
28 |
View Code Duplication |
public function __get(string $property) |
|
|
|
|
65
|
|
|
{ |
66
|
28 |
|
static $scopes = []; |
67
|
28 |
|
$expectedMethod = 'Get' . ucfirst($property); |
68
|
28 |
|
if (true !== method_exists($this, $expectedMethod)) { |
69
|
1 |
|
throw new UndefinedPropertyException(static::class, $property); |
70
|
27 |
|
} elseif (false === $this->CheckPublicScope($property, true)) { |
71
|
1 |
|
throw new NotPublicGetterPropertyException( |
72
|
1 |
|
static::class, |
73
|
1 |
|
$property |
74
|
|
|
); |
75
|
|
|
} |
76
|
|
|
|
77
|
26 |
|
return $this->$expectedMethod(); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* {@inheritdoc} |
82
|
|
|
*/ |
83
|
15 |
View Code Duplication |
public function __set(string $property, $v) |
|
|
|
|
84
|
|
|
{ |
85
|
15 |
|
static $scopes = []; |
86
|
15 |
|
$expectedMethod = 'Set' . ucfirst($property); |
87
|
|
|
if ( |
88
|
15 |
|
true !== method_exists($this, $expectedMethod) |
89
|
|
|
) { |
90
|
1 |
|
throw new PropertyNotWriteableException(static::class, $property); |
91
|
14 |
|
} elseif (false === $this->CheckPublicScope($property, false)) { |
92
|
1 |
|
throw new NotPublicSetterPropertyException( |
93
|
1 |
|
static::class, |
94
|
1 |
|
$property |
95
|
|
|
); |
96
|
|
|
} |
97
|
|
|
|
98
|
13 |
|
return $this->$expectedMethod($v); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* {@inheritdoc} |
103
|
|
|
* |
104
|
|
|
* @see static::NudgePropertyValue() |
105
|
|
|
*/ |
106
|
8 |
|
public function __unset(string $property) : void |
107
|
|
|
{ |
108
|
8 |
|
$this->NudgePropertyValue($property, null); |
109
|
6 |
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* List of properties that can be defined on an implementation. |
113
|
|
|
* |
114
|
|
|
* @return string[] |
115
|
|
|
*/ |
116
|
6 |
|
final public static function DaftObjectProperties() : array |
117
|
|
|
{ |
118
|
6 |
|
return static::PROPERTIES; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* {@inheritdoc} |
123
|
|
|
*/ |
124
|
9 |
|
final public static function DaftObjectNullableProperties() : array |
125
|
|
|
{ |
126
|
9 |
|
return static::NULLABLE_PROPERTIES; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Nudge the state of a given property, marking it as dirty. |
131
|
|
|
* |
132
|
|
|
* @param string $property property being nudged |
133
|
|
|
* @param mixed $value value to nudge property with |
134
|
|
|
* |
135
|
|
|
* @throws UndefinedPropertyException if $property is not in static::DaftObjectProperties() |
136
|
|
|
* @throws PropertyNotNullableException if $property is not in static::DaftObjectNullableProperties() |
137
|
|
|
* @throws PropertyNotRewriteableException if class is write-once read-many and $property was already changed |
138
|
|
|
*/ |
139
|
|
|
abstract protected function NudgePropertyValue( |
140
|
|
|
string $property, |
141
|
|
|
$value |
142
|
|
|
) : void; |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Checks if a type correctly defines it's own id. |
146
|
|
|
* |
147
|
|
|
* @param DaftObject $object |
148
|
|
|
* |
149
|
|
|
* @throws ClassDoesNotImplementClassException if $object is not an implementation of DefinesOwnIdPropertiesInterface |
150
|
|
|
* @throws ClassMethodReturnHasZeroArrayCountException if $object::DaftObjectIdProperties() does not contain at least one property |
151
|
|
|
* @throws ClassMethodReturnIsNotArrayOfStringsException if $object::DaftObjectIdProperties() is not string[] |
152
|
|
|
* @throws UndefinedPropertyException if an id property is not in $object::DaftObjectIdProperties() |
153
|
|
|
*/ |
154
|
33 |
|
final protected static function CheckTypeDefinesOwnIdProperties( |
155
|
|
|
DaftObject $object |
156
|
|
|
) : bool { |
157
|
33 |
|
static $checkedTypes = []; |
158
|
|
|
|
159
|
33 |
|
if (false === isset($checkedTypes[get_class($object)])) { |
160
|
8 |
|
$checkedTypes[get_class($object)] = false; |
161
|
|
|
|
162
|
8 |
|
if (false === ($object instanceof DefinesOwnIdPropertiesInterface)) { |
163
|
1 |
|
throw new ClassDoesNotImplementClassException( |
164
|
1 |
|
get_class($object), |
165
|
1 |
|
DefinesOwnIdPropertiesInterface::class |
166
|
|
|
); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @var DefinesOwnIdPropertiesInterface $object |
171
|
|
|
*/ |
172
|
7 |
|
$object = $object; |
173
|
|
|
|
174
|
7 |
|
$properties = $object::DaftObjectIdProperties(); |
175
|
|
|
|
176
|
7 |
|
if (count($properties) < 1) { |
177
|
1 |
|
throw new ClassMethodReturnHasZeroArrayCountException( |
178
|
1 |
|
get_class($object), |
179
|
1 |
|
'DaftObjectIdProperties' |
180
|
|
|
); |
181
|
|
|
} |
182
|
|
|
|
183
|
6 |
|
foreach ($properties as $property) { |
184
|
6 |
|
if (false === is_string($property)) { |
185
|
1 |
|
throw new ClassMethodReturnIsNotArrayOfStringsException( |
186
|
1 |
|
get_class($object), |
187
|
1 |
|
'DaftObjectIdProperties' |
188
|
|
|
); |
189
|
|
|
} elseif ( |
190
|
5 |
|
false === in_array( |
191
|
5 |
|
$property, |
192
|
5 |
|
$object::DaftObjectProperties(), |
193
|
5 |
|
true |
194
|
|
|
) |
195
|
|
|
) { |
196
|
|
|
throw new UndefinedPropertyException( |
197
|
|
|
get_class($object), |
198
|
|
|
$property |
199
|
|
|
); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
5 |
|
$checkedTypes[get_class($object)] = true; |
204
|
|
|
} |
205
|
|
|
|
206
|
30 |
|
return $checkedTypes[get_class($object)]; |
207
|
|
|
} |
208
|
|
|
|
209
|
35 |
|
private function CheckPublicScope(string $property, bool $getter) : bool |
210
|
|
|
{ |
211
|
35 |
|
static $scopes = []; |
212
|
35 |
|
$expectedMethod = ($getter ? 'Get' : 'Set') . ucwords($property); |
213
|
35 |
|
if (false === isset($scopes[$expectedMethod])) { |
214
|
9 |
|
$scopes[$expectedMethod] = ( |
215
|
|
|
( |
216
|
9 |
|
new ReflectionClass(static::class) |
217
|
9 |
|
)->getMethod($expectedMethod) |
218
|
9 |
|
)->isPublic(); |
219
|
|
|
} |
220
|
|
|
|
221
|
35 |
|
return $scopes[$expectedMethod]; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.