Passed
Push — master ( 537734...074b86 )
by Nelson
03:06
created

PropertiesHandler::ensurePropertyExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * PHP: Nelson Martell Library file
4
 *
5
 * Copyright © 2015-2019 Nelson Martell (http://nelson6e65.github.io)
6
 *
7
 * Licensed under The MIT License (MIT)
8
 * For full copyright and license information, please see the LICENSE
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @copyright 2015-2019 Nelson Martell
12
 * @link      http://nelson6e65.github.io/php_nml/
13
 * @since     0.5.0
14
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License (MIT)
15
 * */
16
17
namespace NelsonMartell;
18
19
use BadMethodCallException;
20
use InvalidArgumentException;
21
22
use NelsonMartell\Extensions\Text;
23
use NelsonMartell\Extensions\MethodExtension;
24
use NelsonMartell\Extensions\PropertyExtension;
25
26
/**
27
 * Enables the class to call, implicitly, getter and setters for its properties, allowing to use properties directly.
28
 *
29
 *
30
 * Restricts get and set actions for a property if there is not getter/setter definicion for that property, by
31
 * encapsulating the class attributes.
32
 *
33
 * You can customize the properties validation/normalization without the need to call other functions/methods outside
34
 * the class _before_ to set the value of _after_ outputs.
35
 *
36
 * In addition, the class will be strict: any access to undefined property will be bloqued and informed in dev time.
37
 *
38
 * Also, any property can be restricted to "read-only" or "write-only" from outside the class if you simply exclude
39
 * the setter or getter for that property, respectively.
40
 *
41
 *
42
 * **Usage:**
43
 *
44
 * ***Example 1:*** Person with normalizations on its name:
45
 *
46
 * ```php
47
 * <?php
48
 * // You can document $name property using: "@property string $name Name of person" in the class definition
49
 * class Person implements \NelsonMartell\IStrictPropertiesContainer {
50
 *     use \NelsonMartell\PropertiesHandler;
51
 *
52
 *     public function __construct($name)
53
 *     {
54
 *         $this->setName($name); // Explicit call the setter inside constructor/class
55
 *     }
56
 *
57
 *     private $name = ''; // Property. 'private' in order to hide from inherited classes and public
58
 *
59
 *     protected function getName() // Getter. 'protected' to hide from public
60
 *     {
61
 *         return ucwords($this->name); // Format the $name output
62
 *     }
63
 *
64
 *     protected function setName($value) // Setter. 'protected' in order to hide from public
65
 *     {
66
 *         $this->name = strtolower($value); // Normalize the $name
67
 *     }
68
 * }
69
 *
70
 * $obj = new Person();
71
 * $obj->name = 'nelson maRtElL'; // Implicit call to setter
72
 * echo $obj->name; // 'Nelson Martell' // Implicit call to getter
73
 * echo $obj->Name; // Throws: InvalidArgumentException: "Name" property do not exists in "Nameable" class.
74
 * ```
75
 *
76
 *
77
 * ***Example 2:*** Same as before, but using a property wrapper (not recommended):
78
 *
79
 * ```php
80
 * <?php
81
 * class Nameable implements NelsonMartell\IStrictPropertiesContainer {
82
 *     use \NelsonMartell\PropertiesHandler;
83
 *
84
 *     private $_name = ''; // Attribute: Stores the value.
85
 *     public $name; // Property wrapper. Declare in order to be detected. Accesible name for the property.
86
 *
87
 *      public function __construct($name)
88
 *     {
89
 *         unset($this->name); // IMPORTANT: Unset the wrapper in order to redirect operations to the getter/setter
90
 *
91
 *         $this->name = $name; // Implicit call to the setter
92
 *     }
93
 *
94
 *     protected function getName()
95
 *     {
96
 *         return ucwords($this->_name);
97
 *     }
98
 *
99
 *     protected function setName($value)
100
 *     {
101
 *         $this->_name = strtolower($value);
102
 *     }
103
 * }
104
 *
105
 * $obj = new Nameable();
106
 * $obj->name = 'nelson maRtElL';
107
 * echo $obj->name; // 'Nelson Martell'
108
 *
109
 * ?>
110
 * ```
111
 *
112
 *
113
 * **Limitations:**
114
 * - You should not define properties wich names only are only different in the first letter upper/lowercase;
115
 *   it will be used the same getter/setter method (since in PHP methods are case-insensitive). In the last
116
 *   example, if you (in addition) define another property called `$Name`, when called, it will
117
 *   be used the same getter and setter method when you access or set both properties (`->Name` and `->name`).
118
 * - Only works for public properties (even if attribute and getter/setter methods are not `public`);
119
 *   this only will avoid the direct use of method (`$obj->getName(); // ERROR`), but the property
120
 *   value still will be accesible in child classes and public scope (`$value = $this->name; // No error`).
121
 * - Getter and Setter methods SHOULD NOT be declared as `private` in child classes if parent already
122
 *   uses this trait.
123
 * - Custom prefixes ability (by implementing ``ICustomPrefixedPropertiesContainer``) is not posible for
124
 *   multiple prefixes in multiples child classes by overriding ``ICustomPrefixedPropertiesContainer`` methods.
125
 *   If you extends a class that already implements it, if you override any methor to return another prefix,
126
 *   parent class properties may be unaccesible (know bug).
127
 * - Avoid the use of custom prefixes and use the standard 'get'/'set' instead. If you need to, maybe you
128
 *   could try to rename methods instead first.
129
 *
130
 * @author Nelson Martell <[email protected]>
131
 * @since 0.5.0
132
 * */
133
trait PropertiesHandler
134
{
135
    /**
136
     * Gets the property value using the auto-magic method `$getterPrefix.$name()` (getter),
137
     * where `$name` is the name of property and `$getterPrefix` is 'get' by default (but can be customized).
138
     *
139
     * @param string $name Property name.
140
     *
141
     * @return mixed
142
     * @throws BadMethodCallException If unable to get the property value.
143
     * @see PropertiesHandler::getPropertyGetter()
144
     * */
145 152
    public function __get($name)
146
    {
147
        try {
148 152
            $getter = static::getPropertyGetter($name);
149 17
        } catch (InvalidArgumentException $error) {
150 17
            $msg = msg('Unable to get the property value in "{0}" class.', get_class($this));
151 17
            throw new BadMethodCallException($msg, 31, $error);
152
        } catch (BadMethodCallException $error) {
153
            $msg = msg('Unable to get the property value in "{0}" class.', get_class($this));
154
            throw new BadMethodCallException($msg, 32, $error);
155
        }
156
157 135
        return $this->$getter();
158
    }
159
160
161
    /**
162
     * Sets the property value using the auto-magic method `$setterPrefix.$name()` (setter),
163
     * where `$name` is the name of property and `$setterPrefix` is 'set' by default (but can be customized).
164
     *
165
     * @param string $name  Property name.
166
     * @param mixed  $value Property value.
167
     *
168
     * @return void
169
     * @throws BadMethodCallException If unable to set property value.
170
     * @see PropertiesHandler::getPropertySetter()
171
     * */
172 33
    public function __set($name, $value)
173
    {
174
        try {
175 33
            $setter = static::getPropertySetter($name);
176 28
        } catch (InvalidArgumentException $error) {
177 28
            $msg = msg('Unable to set the property value in "{0}" class.', get_class($this));
178 28
            throw new BadMethodCallException($msg, 41, $error);
179
        } catch (BadMethodCallException $error) {
180
            $msg = msg('Unable to set the property value in "{0}" class.', get_class($this));
181
            throw new BadMethodCallException($msg, 42, $error);
182
        }
183
184 7
        $this->$setter($value);
185 5
    }
186
187
188
    /**
189
     * Ensures that property provided exists in this class.
190
     *
191
     * @param string $name Property name.
192
     *
193
     * @return string Same property name, but validated.
194
     * @throws InvalidArgumentException If property name is not valid (10) or do not exists (11).
195
     *
196
     * @deprecated 1.0.0 Implementation moved to Extensions\PropertyExtension::ensureIsDefined()
197
     *
198
     * @see PropertyExtension::ensureIsDefined()
199
     */
200 181
    protected static function ensurePropertyExists($name)
201
    {
202 181
        return PropertyExtension::ensureIsDefined($name, get_called_class());
203
    }
204
205
206
    /**
207
     * Ensures that method provided exists in this class.
208
     *
209
     * @param string $name Method name.
210
     *
211
     * @return string Same method name, but validated.
212
     * @throws InvalidArgumentException If method name is not valid (20) or do not exists (21).
213
     *
214
     * @deprecated 1.0.0 Implementation moved to Extensions\MethodExtension::ensureIsDefined()
215
     *
216
     * @see MethodExtension::ensureIsDefined()
217
     */
218 157
    protected static function ensureMethodExists($name)
219
    {
220 157
        return MethodExtension::ensureIsDefined($name, get_called_class());
221
    }
222
223
224
    /**
225
     * Gets the property setter method name.
226
     * You can customize the setter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface.
227
     *
228
     * @param string $name Property name.
229
     *
230
     * @return string
231
     * @throws InvalidArgumentException If property is not valid or has not setter.
232
     * @throws BadMethodCallException If custom prefix is not an ``string`` instance.
233
     * @see ICustomPrefixedPropertiesContainer::getCustomSetterPrefix()
234
     */
235 33
    protected static function getPropertySetter($name)
236
    {
237
        $args = [
238 33
            'class' => get_called_class(),
239
        ];
240
241 33
        $prefix = 'set';
242
243 33
        $args['name'] = static::ensurePropertyExists($name, $args['class']);
0 ignored issues
show
Unused Code introduced by
The call to NelsonMartell\Properties...:ensurePropertyExists() has too many arguments starting with $args['class']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
        /** @scrutinizer ignore-call */ 
244
        $args['name'] = static::ensurePropertyExists($name, $args['class']);

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. Please note the @ignore annotation hint above.

Loading history...
Deprecated Code introduced by
The function NelsonMartell\Properties...:ensurePropertyExists() has been deprecated: 1.0.0 Implementation moved to Extensions\PropertyExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

243
        $args['name'] = /** @scrutinizer ignore-deprecated */ static::ensurePropertyExists($name, $args['class']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
244
245
        try {
246 23
            $setter = static::ensureMethodExists($prefix.$args['name']);
0 ignored issues
show
Deprecated Code introduced by
The function NelsonMartell\Properties...r::ensureMethodExists() has been deprecated: 1.0.0 Implementation moved to Extensions\MethodExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

246
            $setter = /** @scrutinizer ignore-deprecated */ static::ensureMethodExists($prefix.$args['name']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
247 20
        } catch (InvalidArgumentException $error) {
248 20
            $msg = msg('"{name}" property has not a setter method in "{class}".', $args);
249
250 20
            if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) {
251
                // If not available standard setter, check if custom available
252
                try {
253 5
                    $prefix = Text::ensureIsString(static::getCustomSetterPrefix());
254
                } catch (InvalidArgumentException $e) {
255
                    $msg = msg(
256
                        '"{class}::getCustomSetterPrefix" method must to return an string.',
257
                        $args['class']
258
                    );
259
260
                    throw new BadMethodCallException($msg, 31, $e);
261
                }
262
263
                try {
264 5
                    $setter = static::ensureMethodExists($prefix.$args['name']);
0 ignored issues
show
Deprecated Code introduced by
The function NelsonMartell\Properties...r::ensureMethodExists() has been deprecated: 1.0.0 Implementation moved to Extensions\MethodExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

264
                    $setter = /** @scrutinizer ignore-deprecated */ static::ensureMethodExists($prefix.$args['name']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
265 3
                } catch (InvalidArgumentException $e) {
266 5
                    throw new InvalidArgumentException($msg, 32, $e);
267
                }
268
            } else {
269
                // Error for non custom prefixes
270 15
                throw new InvalidArgumentException($msg, 30, $error);
271
            }
272
        }
273
274 7
        return $setter;
275
    }
276
277
278
    /**
279
     * Gets the property getter method name.
280
     * You can customize the getter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface.
281
     *
282
     * @param string $name Property name.
283
     *
284
     * @return string
285
     * @throws InvalidArgumentException If property is not valid or has not getter.
286
     * @throws BadMethodCallException If custom prefix is not an ``string`` instance.
287
     * @see ICustomPrefixedPropertiesContainer::getCustomGetterPrefix()
288
     */
289 152
    protected static function getPropertyGetter($name)
290
    {
291
        $args = [
292 152
            'class' => get_called_class(),
293
        ];
294
295 152
        $prefix = 'get';
296
297 152
        $args['name'] = static::ensurePropertyExists($name, $args['class']);
0 ignored issues
show
Unused Code introduced by
The call to NelsonMartell\Properties...:ensurePropertyExists() has too many arguments starting with $args['class']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

297
        /** @scrutinizer ignore-call */ 
298
        $args['name'] = static::ensurePropertyExists($name, $args['class']);

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. Please note the @ignore annotation hint above.

Loading history...
Deprecated Code introduced by
The function NelsonMartell\Properties...:ensurePropertyExists() has been deprecated: 1.0.0 Implementation moved to Extensions\PropertyExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

297
        $args['name'] = /** @scrutinizer ignore-deprecated */ static::ensurePropertyExists($name, $args['class']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
298
299
        try {
300 138
            $getter = static::ensureMethodExists($prefix.$args['name']);
0 ignored issues
show
Deprecated Code introduced by
The function NelsonMartell\Properties...r::ensureMethodExists() has been deprecated: 1.0.0 Implementation moved to Extensions\MethodExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

300
            $getter = /** @scrutinizer ignore-deprecated */ static::ensureMethodExists($prefix.$args['name']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
301 5
        } catch (InvalidArgumentException $error) {
302 5
            $msg = msg('"{name}" property has not a getter method in "{class}".', $args);
303
304 5
            if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) {
305
                // If not available standard getter, check if custom available
306
                try {
307 3
                    $prefix = Text::ensureIsString(static::getCustomGetterPrefix());
308
                } catch (InvalidArgumentException $e) {
309
                    $msg = msg(
310
                        '"{class}::getCustomGetterPrefix" method must to return an string.',
311
                        $args['class']
312
                    );
313
314
                    throw new BadMethodCallException($msg, 31, $e);
315
                }
316
317
                try {
318 3
                    $getter = static::ensureMethodExists($prefix.$args['name']);
0 ignored issues
show
Deprecated Code introduced by
The function NelsonMartell\Properties...r::ensureMethodExists() has been deprecated: 1.0.0 Implementation moved to Extensions\MethodExtension::ensureIsDefined() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

318
                    $getter = /** @scrutinizer ignore-deprecated */ static::ensureMethodExists($prefix.$args['name']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
319 1
                } catch (InvalidArgumentException $e) {
320 3
                    throw new InvalidArgumentException($msg, 32, $e);
321
                }
322
            } else {
323
                // Error for non custom prefixes
324 2
                throw new InvalidArgumentException($msg, 30, $error);
325
            }
326
        }
327
328 135
        return $getter;
329
    }
330
}
331