|
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 TypeError; |
|
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 TypeError if static::class was previously determined to be incorrectly implemented |
|
48
|
|
|
*/ |
|
49
|
|
|
public function __construct() |
|
50
|
|
|
{ |
|
51
|
|
|
if ( |
|
52
|
|
|
($this instanceof DefinesOwnIdPropertiesInterface) && |
|
53
|
|
|
self::CheckTypeDefinesOwnIdProperties($this) === false |
|
54
|
|
|
) { |
|
55
|
|
|
throw new IncorrectlyImplementedTypeError( |
|
56
|
|
|
get_class($this) . // phpunit coverage does not pick up static::class here |
|
|
|
|
|
|
57
|
|
|
' already determined to be incorrectly implemented' |
|
58
|
|
|
); |
|
59
|
|
|
} |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* Maps param $property to the getter method. |
|
64
|
|
|
* |
|
65
|
|
|
* @param string $property the property being retrieved |
|
66
|
|
|
* |
|
67
|
|
|
* @throws UndefinedPropertyException if a property is undefined |
|
68
|
|
|
* |
|
69
|
|
|
* @return mixed |
|
70
|
|
|
*/ |
|
71
|
|
View Code Duplication |
public function __get(string $property) |
|
|
|
|
|
|
72
|
|
|
{ |
|
73
|
|
|
$expectedMethod = 'Get' . ucfirst($property); |
|
74
|
|
|
if (method_exists($this, $expectedMethod) !== true) { |
|
75
|
|
|
throw new UndefinedPropertyException(static::class, $property); |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
return $this->$expectedMethod(); |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* Maps param $property to the getter method. |
|
83
|
|
|
* |
|
84
|
|
|
* @param string $property the property being retrieved |
|
85
|
|
|
* @param mixed $v |
|
86
|
|
|
* |
|
87
|
|
|
* @throws UndefinedPropertyException if a property is undefined |
|
88
|
|
|
* |
|
89
|
|
|
* @return mixed |
|
90
|
|
|
*/ |
|
91
|
|
View Code Duplication |
public function __set(string $property, $v) |
|
|
|
|
|
|
92
|
|
|
{ |
|
93
|
|
|
$expectedMethod = 'Set' . ucfirst($property); |
|
94
|
|
|
if ( |
|
95
|
|
|
method_exists($this, $expectedMethod) !== true |
|
96
|
|
|
) { |
|
97
|
|
|
throw new PropertyNotWriteableException(static::class, $property); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
return $this->$expectedMethod($v); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* required to support unset($foo->bar). |
|
105
|
|
|
* |
|
106
|
|
|
* @param string $property the property being unset |
|
107
|
|
|
* |
|
108
|
|
|
* @see static::NudgePropertyValue() |
|
109
|
|
|
*/ |
|
110
|
|
|
public function __unset(string $property) : void |
|
111
|
|
|
{ |
|
112
|
|
|
$this->NudgePropertyValue($property, null); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* List of properties that can be defined on an implementation. |
|
117
|
|
|
* |
|
118
|
|
|
* @return string[] |
|
119
|
|
|
*/ |
|
120
|
|
|
final public static function DaftObjectProperties() : array |
|
121
|
|
|
{ |
|
122
|
|
|
return static::PROPERTIES; |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* List of nullable properties that can be defined on an implementation. |
|
127
|
|
|
* |
|
128
|
|
|
* @return string[] |
|
129
|
|
|
*/ |
|
130
|
|
|
final public static function DaftObjectNullableProperties() : array |
|
131
|
|
|
{ |
|
132
|
|
|
return static::NULLABLE_PROPERTIES; |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
/** |
|
136
|
|
|
* Nudge the state of a given property, marking it as dirty. |
|
137
|
|
|
* |
|
138
|
|
|
* @param string $property property being nudged |
|
139
|
|
|
* @param mixed $value value to nudge property with |
|
140
|
|
|
* |
|
141
|
|
|
* @throws UndefinedPropertyException if $property is not in static::DaftObjectProperties() |
|
142
|
|
|
* @throws PropertyNotNullableException if $property is not in static::DaftObjectNullableProperties() |
|
143
|
|
|
*/ |
|
144
|
|
|
abstract protected function NudgePropertyValue( |
|
145
|
|
|
string $property, |
|
146
|
|
|
$value |
|
147
|
|
|
) : void; |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Checks if a type correctly defines it's own id. |
|
151
|
|
|
* |
|
152
|
|
|
* @param DaftObject $object |
|
153
|
|
|
* |
|
154
|
|
|
* @throws TypeError if $object::DaftObjectIdProperties() does not contain at least one property |
|
155
|
|
|
* @throws TypeError if $object::DaftObjectIdProperties() is not string[] |
|
156
|
|
|
* @throws UndefinedPropertyException if an id property is not in $object::DaftObjectIdProperties() |
|
157
|
|
|
*/ |
|
158
|
|
|
final protected static function CheckTypeDefinesOwnIdProperties( |
|
159
|
|
|
DaftObject $object |
|
160
|
|
|
) : bool { |
|
161
|
|
|
static $checkedTypes = []; |
|
162
|
|
|
|
|
163
|
|
|
if (isset($checkedTypes[get_class($object)]) === false) { |
|
164
|
|
|
$checkedTypes[get_class($object)] = false; |
|
165
|
|
|
|
|
166
|
|
|
if (($object instanceof DefinesOwnIdPropertiesInterface) === false) { |
|
167
|
|
|
throw new IncorrectlyImplementedTypeError( |
|
168
|
|
|
get_class($object) . |
|
|
|
|
|
|
169
|
|
|
' does not implement ' . |
|
170
|
|
|
DefinesOwnIdPropertiesInterface::class |
|
171
|
|
|
); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
$properties = $object::DaftObjectIdProperties(); |
|
175
|
|
|
|
|
176
|
|
|
if (count($properties) < 1) { |
|
177
|
|
|
throw new IncorrectlyImplementedTypeError( |
|
178
|
|
|
get_class($object) . |
|
|
|
|
|
|
179
|
|
|
'::DaftObjectIdProperties() must return at least one' . |
|
180
|
|
|
' property' |
|
181
|
|
|
); |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
foreach ($properties as $property) { |
|
185
|
|
|
if (is_string($property) === false) { |
|
186
|
|
|
throw new IncorrectlyImplementedTypeError( |
|
187
|
|
|
get_class($object) . |
|
|
|
|
|
|
188
|
|
|
'::DaftObjectIdProperties() does not return string[]' |
|
189
|
|
|
); |
|
190
|
|
|
} elseif ( |
|
191
|
|
|
in_array( |
|
192
|
|
|
$property, |
|
193
|
|
|
$object::DaftObjectProperties(), |
|
194
|
|
|
true |
|
195
|
|
|
) === false |
|
196
|
|
|
) { |
|
197
|
|
|
throw new UndefinedPropertyException( |
|
198
|
|
|
get_class($object), |
|
199
|
|
|
$property |
|
200
|
|
|
); |
|
201
|
|
|
} |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
$checkedTypes[get_class($object)] = true; |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
return $checkedTypes[get_class($object)]; |
|
208
|
|
|
} |
|
209
|
|
|
} |
|
210
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignorePhpDoc annotation to the duplicate definition and it will be ignored.