Completed
Push — master ( a2c643...e562f7 )
by Nelson
04:00
created

PropertiesHandler   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Test Coverage

Coverage 73.81%

Importance

Changes 0
Metric Value
eloc 94
dl 0
loc 233
rs 10
c 0
b 0
f 0
ccs 62
cts 84
cp 0.7381
wmc 25

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getPropertySetter() 0 40 5
A __set() 0 13 3
A getPropertyGetter() 0 40 5
A ensurePropertyExists() 0 31 6
A ensureMethodExists() 0 20 3
A __get() 0 13 3
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     0.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
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 107 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
67
 *
68
 * ?>
69
 * ```
70
 *
71
 * ## Notes:
72
 * - You should not define properties wich names only are only different in the first letter upper/lowercase;
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 109 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
73
 *   it will be used the same getter/setter method (since in PHP methods are case-insensitive). In the last
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 107 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
74
 *   example, if you (in addition) define the `public $name` and `unset($this->name)` in the constructor, it will
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 113 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
75
 *   be used the same getter and setter method when you access or set both properties (`->Name` and `->name`).
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 110 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
76
 * - Only works for public properties (even if you declare visibility of getter/setter methods as `private`
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 107 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
77
 *   or `protected`); this only will avoid the direct use of method (``$obj->getName(); // ERROR``), but property
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 113 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
78
 *   value still will be available in child classes and main (``$value = $this->name; // No error``).
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 101 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
79
 * - Getter and Setter methods SHOULD NOT be declared as ``private`` in child classes if parent already
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 103 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
80
 *   uses this trait.
81
 * - Custom prefixes ability (by implementing ``ICustomPrefixedPropertiesContainer``) is not posible for
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 104 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
82
 *   multiple prefixes in multiples child classes by overriding ``ICustomPrefixedPropertiesContainer`` methods.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 111 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
83
 *   If you extends a class that already implements it, if you override any methor to return another prefix,
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 108 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 105 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
86
 *   could try to rename methods instead first.
87
 *
88
 * @author Nelson Martell <[email protected]>
89
 * @since 0.5.0
90
 * */
91
trait PropertiesHandler
92
{
93
    /**
94
     * Gets the property value using the auto-magic method `$getterPrefix.$name()` (getter),
95
     * where `$name` is the name of property and `$getterPrefix` is 'get' by default (but can be customized).
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 109 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
96
     *
97
     * @param string $name Property name.
98
     *
99
     * @return mixed
100
     * @throws BadMethodCallException If unable to get the property value.
101
     * @see PropertiesHandler::getPropertyGetter()
102
     * */
103 327
    public function __get($name)
104
    {
105
        try {
106 327
            $getter = static::getPropertyGetter($name);
107 4
        } catch (InvalidArgumentException $error) {
108 4
            $msg = msg('Unable to get the property value in "{0}" class.', get_class($this));
109 4
            throw new BadMethodCallException($msg, 31, $error);
110
        } catch (BadMethodCallException $error) {
111
            $msg = msg('Unable to get the property value in "{0}" class.', get_class($this));
112
            throw new BadMethodCallException($msg, 32, $error);
113
        }
114
115 327
        return $this->$getter();
116
    }
117
118
119
    /**
120
     * Sets the property value using the auto-magic method `$setterPrefix.$name()` (setter),
121
     * where `$name` is the name of property and `$setterPrefix` is 'set' by default (but can be customized).
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 109 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
122
     *
123
     * @param string $name  Property name.
124
     * @param mixed  $value Property value.
125
     *
126
     * @return void
127
     * @throws BadMethodCallException If unable to set property value.
128
     * @see PropertiesHandler::getPropertySetter()
129
     * */
130 34
    public function __set($name, $value)
131
    {
132
        try {
133 34
            $setter = static::getPropertySetter($name);
134 28
        } catch (InvalidArgumentException $error) {
135 28
            $msg = msg('Unable to set the property value in "{0}" class.', get_class($this));
136 28
            throw new BadMethodCallException($msg, 41, $error);
137
        } catch (BadMethodCallException $error) {
138
            $msg = msg('Unable to set the property value in "{0}" class.', get_class($this));
139
            throw new BadMethodCallException($msg, 42, $error);
140
        }
141
142 8
        $this->$setter($value);
143 6
    }
144
145
146
    /**
147
     * Ensures that property provided exists in this class.
148
     *
149
     * @param string $name Property name.
150
     *
151
     * @return string Same property name, but validated.
152
     * @throws InvalidArgumentException If property name is not valid (10) or do not exists (11).
153
     */
154 328
    protected static function ensurePropertyExists($name)
155
    {
156
        $args = [
157 328
            'class'    => get_called_class(),
158
        ];
159
160
        try {
161 328
            $args['property'] = Text::ensureIsValidVarName($name);
162
        } catch (InvalidArgumentException $error) {
163
            $msg = msg('Property name is not valid.');
164
            throw new InvalidArgumentException($msg, 10, $error);
165
        }
166
167 328
        if (!property_exists($args['class'], $args['property'])) {
168
            // Check in parent classes for private property
169 15
            $current = $args['class'];
170 15
            $exists = false;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
171 15
            while ($current = get_parent_class($current) and !$exists) {
172 11
                $exists = property_exists($current, $args['property']);
173
            }
174
175 15
            if (!$exists) {
176 12
                $msg = msg(
177 12
                    '"{property}" property do not exists in "{class}" class or parent classes.',
178 12
                    $args
179
                );
180 12
                throw new InvalidArgumentException($msg, 11);
181
            }
182
        }
183
184 328
        return $name;
185
    }
186
187
188
    /**
189
     * Ensures that method provided exists in this class.
190
     *
191
     * @param string $name Method name.
192
     *
193
     * @return string Same method name, but validated.
194
     * @throws InvalidArgumentException If method name is not valid (20) or do not exists (21).
195
     */
196 328
    protected static function ensureMethodExists($name)
197
    {
198
        $args = [
199 328
            'class'  => get_called_class(),
200
        ];
201
202
        try {
203 328
            $args['method'] = Text::ensureIsValidVarName($name);
204
        } catch (InvalidArgumentException $error) {
205
            $msg = msg('Method name is not valid.');
206
            throw new InvalidArgumentException($msg, 20, $error);
207
        }
208
209 328
        if (method_exists($args['class'], $args['method']) === false) {
210 23
            $msg = msg('"{class}::{method}" do not exists.', $args);
211
212 23
            throw new InvalidArgumentException($msg, 21);
213
        }
214
215 328
        return $name;
216
    }
217
218
219
    /**
220
     * Gets the property setter method name.
221
     * You can customize the setter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 108 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
222
     *
223
     * @param string $name Property name.
224
     *
225
     * @return string
226
     * @throws InvalidArgumentException If property is not valid or has not setter.
227
     * @throws BadMethodCallException If custom prefix is not an ``string`` instance.
228
     * @see ICustomPrefixedPropertiesContainer::getCustomSetterPrefix()
229
     */
230 34
    protected static function getPropertySetter($name)
231
    {
232
        $args = [
233 34
            'class' => get_called_class(),
234
        ];
235
236 34
        $prefix = 'set';
237
238 34
        $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

238
        /** @scrutinizer ignore-call */ 
239
        $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...
239
240
        try {
241 24
            $setter = static::ensureMethodExists($prefix.$args['name']);
242 20
        } catch (InvalidArgumentException $error) {
243 20
            $msg = msg('"{name}" property has not a setter method in "{class}".', $args);
244
245 20
            if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) {
246
                // If not available standard setter, check if custom available
247
                try {
248 5
                    $prefix = Text::ensureIsString(static::getCustomSetterPrefix());
249
                } catch (InvalidArgumentException $e) {
250
                    $msg = msg(
251
                        '"{class}::getCustomSetterPrefix" method must to return an string.',
252
                        $args['class']
253
                    );
254
255
                    throw new BadMethodCallException($msg, 31, $e);
256
                }
257
258
                try {
259 5
                    $setter = static::ensureMethodExists($prefix.$args['name']);
260 3
                } catch (InvalidArgumentException $e) {
261 5
                    throw new InvalidArgumentException($msg, 32, $e);
262
                }
263
            } else {
264
                // Error for non custom prefixes
265 15
                throw new InvalidArgumentException($msg, 30, $error);
266
            }
267
        }
268
269 8
        return $setter;
270
    }
271
272
273
    /**
274
     * Gets the property getter method name.
275
     * You can customize the getter prefix by implementing ``ICustomPrefixedPropertiesContainer`` interface.
1 ignored issue
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 108 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
276
     *
277
     * @param string $name Property name.
278
     *
279
     * @return string
280
     * @throws InvalidArgumentException If property is not valid or has not getter.
281
     * @throws BadMethodCallException If custom prefix is not an ``string`` instance.
282
     * @see ICustomPrefixedPropertiesContainer::getCustomGetterPrefix()
283
     */
284 327
    protected static function getPropertyGetter($name)
285
    {
286
        $args = [
287 327
            'class' => get_called_class(),
288
        ];
289
290 327
        $prefix = 'get';
291
292 327
        $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

292
        /** @scrutinizer ignore-call */ 
293
        $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...
293
294
        try {
295 327
            $getter = static::ensureMethodExists($prefix.$args['name']);
296 4
        } catch (InvalidArgumentException $error) {
297 4
            $msg = msg('"{name}" property has not a getter method in "{class}".', $args);
298
299 4
            if (is_subclass_of($args['class'], ICustomPrefixedPropertiesContainer::class)) {
300
                // If not available standard getter, check if custom available
301
                try {
302 3
                    $prefix = Text::ensureIsString(static::getCustomGetterPrefix());
303
                } catch (InvalidArgumentException $e) {
304
                    $msg = msg(
305
                        '"{class}::getCustomGetterPrefix" method must to return an string.',
306
                        $args['class']
307
                    );
308
309
                    throw new BadMethodCallException($msg, 31, $e);
310
                }
311
312
                try {
313 3
                    $getter = static::ensureMethodExists($prefix.$args['name']);
314 1
                } catch (InvalidArgumentException $e) {
315 3
                    throw new InvalidArgumentException($msg, 32, $e);
316
                }
317
            } else {
318
                // Error for non custom prefixes
319 1
                throw new InvalidArgumentException($msg, 30, $error);
320
            }
321
        }
322
323 327
        return $getter;
324
    }
325
}
326