1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* PHP: Nelson Martell Library file |
4
|
|
|
* |
5
|
|
|
* Content: |
6
|
|
|
* - Trait definition: [NelsonMartell] PropertiesHandler |
7
|
|
|
* |
8
|
|
|
* Copyright © 2015-2017 Nelson Martell (http://nelson6e65.github.io) |
9
|
|
|
* |
10
|
|
|
* Licensed under The MIT License (MIT) |
11
|
|
|
* For full copyright and license information, please see the LICENSE |
12
|
|
|
* Redistributions of files must retain the above copyright notice. |
13
|
|
|
* |
14
|
|
|
* @copyright 2015-2017 Nelson Martell |
15
|
|
|
* @link http://nelson6e65.github.io/php_nml/ |
16
|
|
|
* @since v0.5.0 |
17
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php The MIT License (MIT) |
18
|
|
|
* */ |
19
|
|
|
|
20
|
|
|
namespace NelsonMartell; |
21
|
|
|
|
22
|
|
|
use NelsonMartell\Extensions\Text; |
23
|
|
|
use \BadMethodCallException; |
24
|
|
|
use \InvalidArgumentException; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Enables the class to use properties, by encapsulating class attributes in order to use with |
28
|
|
|
* auto-setters/getters methods instead of direct access. |
29
|
|
|
* |
30
|
|
|
* Using this trail will restrict get and set actions for a property if there is not defined in |
31
|
|
|
* the class or if there is not a getter or setter method (respectively) for that property. |
32
|
|
|
* |
33
|
|
|
* So, you MUST (1) create the property in the class and (2) then unset it in the constructor |
34
|
|
|
* (*this requirements will change in next releases to be more 'auto-magic'*). |
35
|
|
|
* You can, also, expose read-only attributes to be public by creating only a getter method and declare |
36
|
|
|
* visibility of attribute as private. |
37
|
|
|
* |
38
|
|
|
* @example |
39
|
|
|
* ```php |
40
|
|
|
* <?php |
41
|
|
|
* class Nameable implements NelsonMartell\IStrictPropertiesContainer { |
42
|
|
|
* use NelsonMartell\PropertiesHandler; |
43
|
|
|
* |
44
|
|
|
* public function __construct() |
45
|
|
|
* { |
46
|
|
|
* unset($this->Name); // (2) |
47
|
|
|
* } |
48
|
|
|
* |
49
|
|
|
* private $_name = ''; // Attribute: Stores the value. |
50
|
|
|
* public $Name; // (1) Property: Accesible name for the property. |
51
|
|
|
* |
52
|
|
|
* public function getName() |
53
|
|
|
* { |
54
|
|
|
* return ucwords($this->_name); |
55
|
|
|
* } |
56
|
|
|
* |
57
|
|
|
* public function setName($value) |
58
|
|
|
* { |
59
|
|
|
* $this->_name = strtolower($value); |
60
|
|
|
* } |
61
|
|
|
* } |
62
|
|
|
* |
63
|
|
|
* $obj = new Nameable(); |
64
|
|
|
* $obj->Name = 'nelson maRtElL'; |
65
|
|
|
* echo $obj->Name; // 'Nelson Martell' |
66
|
|
|
* echo $obj->name; // Throws: InvalidArgumentException: "name" property do not exists in "Nameable" class. |
67
|
|
|
* |
68
|
|
|
* ?> |
69
|
|
|
* ``` |
70
|
|
|
* |
71
|
|
|
* ## Notes: |
72
|
|
|
* - You should not define properties wich names only are only different in the first letter upper/lowercase; |
73
|
|
|
* it will be used the same getter/setter method (since in PHP methods are case-insensitive). In the last |
74
|
|
|
* example, if you (in addition) define the `public $name` and `unset($this->name)` in the constructor, it will |
75
|
|
|
* be used the same getter and setter method when you access or set both properties (`->Name` and `->name`). |
76
|
|
|
* - Only works for public properties (even if you declare visibility of getter/setter methods as `private` |
77
|
|
|
* or `protected`); this only will avoid the direct use of method (``$obj->getName(); // ERROR``), but property |
78
|
|
|
* value still will be available in child classes and main (``$value = $this->name; // No error``). |
79
|
|
|
* - Getter and Setter methods SHOULD NOT be declared as ``private`` in child classes if parent already |
80
|
|
|
* uses this trait. |
81
|
|
|
* - Custom prefixes ability (by implementing ``ICustomPrefixedPropertiesContainer``) is not posible for |
82
|
|
|
* multiple prefixes in multiples child classes by overriding ``ICustomPrefixedPropertiesContainer`` methods. |
83
|
|
|
* If you extends a class that already implements it, if you override any methor to return another prefix, |
84
|
|
|
* parent class properties may be unaccesible (know bug). |
85
|
|
|
* - Avoid the use of custom prefixes and use the standard 'get'/'set' instead. If you need to, maybe you |
86
|
|
|
* could try to rename methods instead first. |
87
|
|
|
* |
88
|
|
|
* @author Nelson Martell <[email protected]> |
89
|
|
|
* */ |
90
|
|
|
trait PropertiesHandler |
91
|
|
|
{ |
92
|
|
|
/** |
93
|
|
|
* Gets the property value using the auto-magic method `$getterPrefix.$name()` (getter), |
94
|
|
|
* where `$name` is the name of property and `$getterPrefix` is 'get' by default (but can be customized). |
95
|
|
|
* |
96
|
|
|
* @param string $name Property name. |
97
|
|
|
* |
98
|
|
|
* @return mixed |
99
|
|
|
* @throws BadMethodCallException If unable to get the property value. |
100
|
|
|
* @see PropertiesHandler::getPropertyGetter() |
101
|
|
|
* */ |
102
|
271 |
View Code Duplication |
public function __get($name) |
|
|
|
|
103
|
|
|
{ |
104
|
|
|
try { |
105
|
271 |
|
$getter = static::getPropertyGetter($name); |
106
|
3 |
|
} catch (InvalidArgumentException $error) { |
107
|
3 |
|
$msg = msg('Unable to get the property value in "{0}" class.', get_class($this)); |
108
|
3 |
|
throw new BadMethodCallException($msg, 31, $error); |
109
|
|
|
} catch (BadMethodCallException $error) { |
110
|
|
|
$msg = msg('Unable to get the property value in "{0}" class.', get_class($this)); |
111
|
|
|
throw new BadMethodCallException($msg, 32, $error); |
112
|
|
|
} |
113
|
|
|
|
114
|
271 |
|
return $this->$getter(); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Sets the property value using the auto-magic method `$setterPrefix.$name()` (setter), |
120
|
|
|
* where `$name` is the name of property and `$setterPrefix` is 'set' by default (but can be customized). |
121
|
|
|
* |
122
|
|
|
* @param string $name Property name. |
123
|
|
|
* @param mixed $value Property value. |
124
|
|
|
* |
125
|
|
|
* @return void |
126
|
|
|
* @throws BadMethodCallException If unable to set property value. |
127
|
|
|
* @see PropertiesHandler::getPropertySetter() |
128
|
|
|
* */ |
129
|
14 |
View Code Duplication |
public function __set($name, $value) |
|
|
|
|
130
|
|
|
{ |
131
|
|
|
try { |
132
|
14 |
|
$setter = static::getPropertySetter($name); |
133
|
11 |
|
} catch (InvalidArgumentException $error) { |
134
|
11 |
|
$msg = msg('Unable to set the property value in "{0}" class.', get_class($this)); |
135
|
11 |
|
throw new BadMethodCallException($msg, 41, $error); |
136
|
|
|
} catch (BadMethodCallException $error) { |
137
|
|
|
$msg = msg('Unable to set the property value in "{0}" class.', get_class($this)); |
138
|
|
|
throw new BadMethodCallException($msg, 42, $error); |
139
|
|
|
} |
140
|
|
|
|
141
|
4 |
|
$this->$setter($value); |
142
|
3 |
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Ensures that property provided exists in this class. |
147
|
|
|
* |
148
|
|
|
* @param string $name Property name. |
149
|
|
|
* |
150
|
|
|
* @return string Same property name, but validated. |
151
|
|
|
* @throws InvalidArgumentException If property name is not valid (10) or do not exists (11). |
152
|
|
|
*/ |
153
|
272 |
|
protected static function ensurePropertyExists($name) |
154
|
|
|
{ |
155
|
|
|
$args = [ |
156
|
272 |
|
'class' => get_called_class(), |
157
|
|
|
]; |
158
|
|
|
|
159
|
|
|
try { |
160
|
272 |
|
$args['property'] = Text::ensureIsValidVarName($name); |
161
|
|
|
} catch (InvalidArgumentException $error) { |
162
|
|
|
$msg = msg('Property name is not valid.'); |
163
|
|
|
throw new InvalidArgumentException($msg, 10, $error); |
164
|
|
|
} |
165
|
|
|
|
166
|
272 |
|
if (!property_exists($args['class'], $args['property'])) { |
167
|
|
|
// Check in parent classes for private property |
168
|
8 |
|
$current = $args['class']; |
169
|
8 |
|
$exists = false; |
|
|
|
|
170
|
8 |
|
while ($current = get_parent_class($current) and !$exists) { |
|
|
|
|
171
|
5 |
|
$exists = property_exists($current, $args['property']); |
172
|
|
|
} |
173
|
|
|
|
174
|
8 |
|
if (!$exists) { |
175
|
6 |
|
$msg = msg( |
176
|
6 |
|
'"{property}" property do not exists in "{class}" class or parent classes.', |
177
|
6 |
|
$args |
178
|
|
|
); |
179
|
6 |
|
throw new InvalidArgumentException($msg, 11); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
272 |
|
return $name; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Ensures that method provided exists in this class. |
189
|
|
|
* |
190
|
|
|
* @param string $name Method name. |
191
|
|
|
* |
192
|
|
|
* @return string Same method name, but validated. |
193
|
|
|
* @throws InvalidArgumentException If method name is not valid (20) or do not exists (21). |
194
|
|
|
*/ |
195
|
272 |
|
protected static function ensureMethodExists($name) |
196
|
|
|
{ |
197
|
|
|
$args = [ |
198
|
272 |
|
'class' => get_called_class(), |
199
|
|
|
]; |
200
|
|
|
|
201
|
|
|
try { |
202
|
272 |
|
$args['method'] = Text::ensureIsValidVarName($name); |
203
|
|
|
} catch (InvalidArgumentException $error) { |
204
|
|
|
$msg = msg('Method name is not valid.'); |
205
|
|
|
throw new InvalidArgumentException($msg, 20, $error); |
206
|
|
|
} |
207
|
|
|
|
208
|
272 |
|
if (method_exists($args['class'], $args['method']) === false) { |
209
|
12 |
|
$msg = msg('"{class}::{method}" do not exists.', $args); |
210
|
|
|
|
211
|
12 |
|
throw new InvalidArgumentException($msg, 21); |
212
|
|
|
} |
213
|
|
|
|
214
|
272 |
|
return $name; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Gets the property setter method name. |
220
|
|
|
* You can customize the setter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface. |
221
|
|
|
* |
222
|
|
|
* @param string $name Property name. |
223
|
|
|
* |
224
|
|
|
* @return string |
225
|
|
|
* @throws InvalidArgumentException If property is not valid or has not setter. |
226
|
|
|
* @throws BadMethodCallException If custom prefix is not an ``string`` instance. |
227
|
|
|
* @see ICustomPrefixedPropertiesContainer::getCustomSetterPrefix() |
228
|
|
|
*/ |
229
|
14 |
View Code Duplication |
protected static function getPropertySetter($name) |
|
|
|
|
230
|
|
|
{ |
231
|
|
|
$args = [ |
|
|
|
|
232
|
14 |
|
'class' => get_called_class(), |
233
|
|
|
]; |
234
|
|
|
|
235
|
14 |
|
$prefix = 'set'; |
236
|
|
|
|
237
|
14 |
|
$args['name'] = static::ensurePropertyExists($name, $args['class']); |
|
|
|
|
238
|
|
|
|
239
|
|
|
try { |
240
|
10 |
|
$setter = static::ensureMethodExists($prefix.$args['name']); |
241
|
8 |
|
} catch (InvalidArgumentException $error) { |
242
|
8 |
|
$msg = msg('"{name}" property has not a setter method in "{class}".', $args); |
243
|
|
|
|
244
|
8 |
|
if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) { |
245
|
|
|
// If not available standard setter, check if custom available |
246
|
|
|
try { |
247
|
2 |
|
$prefix = Text::ensureIsString(static::getCustomSetterPrefix()); |
248
|
|
|
} catch (InvalidArgumentException $e) { |
249
|
|
|
$msg = msg( |
250
|
|
|
'"{class}::getCustomSetterPrefix" method must to return an string.', |
251
|
|
|
$args['class'] |
252
|
|
|
); |
253
|
|
|
|
254
|
|
|
throw new BadMethodCallException($msg, 31, $e); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
try { |
258
|
2 |
|
$setter = static::ensureMethodExists($prefix.$args['name']); |
259
|
1 |
|
} catch (InvalidArgumentException $e) { |
260
|
1 |
|
throw new InvalidArgumentException($msg, 32, $e); |
261
|
|
|
} |
262
|
|
|
} else { |
263
|
|
|
// Error for non custom prefixes |
264
|
6 |
|
throw new InvalidArgumentException($msg, 30, $error); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
4 |
|
return $setter; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Gets the property getter method name. |
274
|
|
|
* You can customize the getter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface. |
275
|
|
|
* |
276
|
|
|
* @param string $name Property name. |
277
|
|
|
* |
278
|
|
|
* @return string |
279
|
|
|
* @throws InvalidArgumentException If property is not valid or has not getter. |
280
|
|
|
* @throws BadMethodCallException If custom prefix is not an ``string`` instance. |
281
|
|
|
* @see ICustomPrefixedPropertiesContainer::getCustomGetterPrefix() |
282
|
|
|
*/ |
283
|
271 |
View Code Duplication |
protected static function getPropertyGetter($name) |
|
|
|
|
284
|
|
|
{ |
285
|
|
|
$args = [ |
|
|
|
|
286
|
271 |
|
'class' => get_called_class(), |
287
|
|
|
]; |
288
|
|
|
|
289
|
271 |
|
$prefix = 'get'; |
290
|
|
|
|
291
|
271 |
|
$args['name'] = static::ensurePropertyExists($name, $args['class']); |
|
|
|
|
292
|
|
|
|
293
|
|
|
try { |
294
|
271 |
|
$getter = static::ensureMethodExists($prefix.$args['name']); |
295
|
4 |
|
} catch (InvalidArgumentException $error) { |
296
|
4 |
|
$msg = msg('"{name}" property has not a getter method in "{class}".', $args); |
297
|
|
|
|
298
|
4 |
|
if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) { |
299
|
|
|
// If not available standard getter, check if custom available |
300
|
|
|
try { |
301
|
4 |
|
$prefix = Text::ensureIsString(static::getCustomGetterPrefix()); |
302
|
|
|
} catch (InvalidArgumentException $e) { |
303
|
|
|
$msg = msg( |
304
|
|
|
'"{class}::getCustomGetterPrefix" method must to return an string.', |
305
|
|
|
$args['class'] |
306
|
|
|
); |
307
|
|
|
|
308
|
|
|
throw new BadMethodCallException($msg, 31, $e); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
try { |
312
|
4 |
|
$getter = static::ensureMethodExists($prefix.$args['name']); |
313
|
1 |
|
} catch (InvalidArgumentException $e) { |
314
|
1 |
|
throw new InvalidArgumentException($msg, 32, $e); |
315
|
|
|
} |
316
|
|
|
} else { |
317
|
|
|
// Error for non custom prefixes |
318
|
|
|
throw new InvalidArgumentException($msg, 30, $error); |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
322
|
271 |
|
return $getter; |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
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.