Passed
Branch master (448315)
by Antarès
03:12 queued 23s
created

setPropertiesConstraintsValidationDisabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Accessible;
4
5
use \Accessible\MethodManager\MethodCallManager;
6
use \Accessible\MethodManager\CollectionManager;
7
use \Accessible\MethodManager\ListManager;
8
use \Accessible\MethodManager\MapManager;
9
use \Accessible\MethodManager\SetManager;
10
use \Accessible\Reader\AutoConstructReader;
11
use \Accessible\Reader\AccessReader;
12
use \Accessible\Reader\AssociationReader;
13
use \Accessible\Reader\ConstraintsReader;
14
use \Accessible\Reader\CollectionsReader;
15
16
trait AutomatedBehaviorTrait
17
{
18
    /**
19
     * The list of access rights on each property of the object.
20
     *
21
     * @var array
22
     */
23
    private $_accessProperties;
24
25
    /**
26
     * The list of collection properties and their item names.
27
     * Ex: [
28
     *   "byItemName" => "user" => ["property" => "users", "behavior" => "list", "methods" => ["add", "remove"]],
29
     *   "byProperty" => "users" => ["itemName" => "user", "behavior" => "list", "methods" => ["add", "remove"]]
30
     * ]
31
     *
32
     * @var array
33
     */
34
    private $_collectionsItemNames;
35
36
    /**
37
     * The list of associations for each property
38
     * Ex: ["products" => ["property" => "cart", "association" => "inverted"]]
39
     *
40
     * @var array
41
     */
42
    private $_associationsList;
43
44
    /**
45
     * Indicates wether the constraints validation should be enabled or not.
46
     *
47
     * @var boolean
48
     */
49
    private $_constraintsValidationEnabled;
50
51
    /**
52
     * Indicates wether getPropertiesInfo() has been called or not.
53
     *
54
     * @var boolean
55
     */
56
    private $_automatedBehaviorInitialized = false;
57
58
    /**
59
     * Directly calls the initialization method.
60
     */
61 28
    public function __construct()
62
    {
63 28
        $this->initializeProperties(func_get_args());
64 27
    }
65
66
    /**
67
     * Indicates if the properties constraints validation is enabled.
68
     *
69
     * @return boolean
70
     */
71
    public function isPropertiesConstraintsValidationEnabled()
72
    {
73
        return $this->_constraintsValidationEnabled;
74
    }
75
76
    /**
77
     * Enable (or disable) the properties constraints validation.
78
     *
79
     * @param boolean $enabled
80
     */
81 1
    public function setPropertiesConstraintsValidationEnabled($enabled = true)
82
    {
83 1
        $this->_constraintsValidationEnabled = $enabled;
84 1
    }
85
86
    /**
87
     * Disable (or enable) the properties constraints validation.
88
     *
89
     * @param boolean $disabled
90
     */
91 1
    public function setPropertiesConstraintsValidationDisabled($disabled = true)
92
    {
93 1
        $this->_constraintsValidationEnabled = !$disabled;
94 1
    }
95
96
    /**
97
     * This function will be called each time a getter or a setter that is not
98
     * already defined in the class is called.
99
     *
100
     * @param  string $name The name of the called function.
101
     *                      It must be a getter or a setter.
102
     * @param  array  $args The array of arguments for the called function.
103
     *                      It should be empty for a getter call,
104
     *                      and should have one item for a setter call.
105
     *
106
     * @return mixed    The value that should be returned by the function called if it is a getter,
107
     *                  the object itself if the function called is a setter.
108
     *
109
     * @throws \BadMethodCallException      When the method called is neither a getter nor a setter,
110
     *         						   		or if the access right has not be given for this method,
111
     *         						     	or if the method is a setter called without argument.
112
     * @throws \InvalidArgumentException    When the argument given to the method called (as a setter)
113
     *         								does not satisfy the constraints attached to the property
114
     *         								to modify.
115
     */
116 26
    public function __call($name, array $args)
117
    {
118 26
        $this->getPropertiesInfo();
119
120 26
        $methodCallInfo = $this->getMethodCallInfo($name);
121 24
        $method = $methodCallInfo['method'];
122 24
        $property = $methodCallInfo['property'];
123 24
        $collectionProperties = $methodCallInfo['collectionProperties'];
124
125 24
        $valuesToUpdate = array();
126
127
        switch ($method) {
128 24
            case 'get':
129 24
            case 'is':
130 19
                return $this->$property;
131
132 21
            case 'set':
133
                // a setter should have exactly one argument
134 14
                MethodCallManager::assertArgsNumber(1, $args);
135
                // we set a collection here if there is an association with it
136
                if (
137 14
                    !empty($this->_collectionsItemNames['byProperty'][$property])
138 14
                    && !(empty($this->_associationsList[$property]))
139 14
                ) {
140
                    $itemName = $this->_collectionsItemNames['byProperty'][$property]['itemName'];
141
                    $propertyAddMethod = 'add' . strtoupper(substr($itemName, 0, 1)) . substr($itemName, 1);
142
                    $propertyRemoveMethod = 'remove' . strtoupper(substr($itemName, 0, 1)) . substr($itemName, 1);
143
144
                    foreach ($this->$property as $item) {
145
                        $this->$propertyRemoveMethod($item);
146
                    }
147
                    foreach ($args[0] as $item) {
148
                        $this->$propertyAddMethod($item);
149
                    }
150
                }
151
                // we set a regular property here
152
                else {
153 14
                    $oldValue = $this->$property;
154 14
                    $newValue = $args[0];
155
                    $valuesToUpdate = array(
156 14
                        'oldValue' => $oldValue,
157
                        'newValue' => $newValue
158 14
                    );
159
                    // check that the setter argument respects the property constraints
160 14
                    $this->assertPropertyValue($property, $newValue);
161
162 11
                    if ($oldValue !== $newValue) {
163 11
                        $this->$property = $newValue;
164 11
                    }
165
                }
166 11
                break;
167
168 10
            case 'add':
169 10
            case 'remove':
170 10
                $valueToUpdate = ($method === 'add') ? 'newValue' : 'oldValue';
171 10
                switch ($collectionProperties['behavior']) {
172 10
                    case 'list':
173 4
                        ListManager::$method($this->$property, $args);
174 4
                        $valuesToUpdate[$valueToUpdate] = $args[0];
175 4
                        break;
176 6
                    case 'map':
177 2
                        MapManager::$method($this->$property, $args);
178 1
                        break;
179 4
                    case 'set':
180 4
                        SetManager::$method($this->$property, $args);
181 4
                        $valuesToUpdate[$valueToUpdate] = $args[0];
182 4
                        break;
183 9
                }
184 9
                break;
185
        }
186
187
        // manage associations
188 17
        if (in_array($method, array('set', 'add', 'remove'))) {
189 17
            $this->updatePropertyAssociation($property, $valuesToUpdate);
190 17
        }
191
192 17
        return $this;
193
    }
194
195
    /**
196
     * Validates the given value compared to given property constraints.
197
     * If the value is not valid, an InvalidArgumentException will be thrown.
198
     *
199
     * @param  string $property The name of the reference property.
200
     * @param  mixed  $value    The value to check.
201
     *
202
     * @throws \InvalidArgumentException If the value is not valid.
203
     */
204 29
    protected function assertPropertyValue($property, $value)
205
    {
206 29
        $this->getPropertiesInfo();
207
208 29
        if ($this->_constraintsValidationEnabled) {
209 28
            $constraintsViolations = ConstraintsReader::validatePropertyValue($this, $property, $value);
210 28
            if ($constraintsViolations->count()) {
211 5
                $errorMessage = "Argument given is invalid; its constraints validation failed for property $property with the following messages: \"";
212 5
                $errorMessageList = array();
213 5
                foreach ($constraintsViolations as $violation) {
214 5
                    $errorMessageList[] = $violation->getMessage();
215 5
                }
216 5
                $errorMessage .= implode("\", \n\"", $errorMessageList) . "\".";
217
218 5
                throw new \InvalidArgumentException($errorMessage);
219
            }
220 27
        }
221 29
    }
222
223
    /**
224
     * Update the property associated to the given property.
225
     * You can pass the old or the new value given to the property.
226
     *
227
     * @param  string $property The property of the current class to update
228
     * @param  object $values   An array of old a new value under the following form:
229
     *                          ['oldValue' => $oldvalue, 'newValue' => $newValue]
230
     *                          If one of theses values is not given, it will simply not be updated.
231
     */
232 29
    protected function updatePropertyAssociation($property, array $values)
233
    {
234 29
        $this->getPropertiesInfo();
235
236 29
        if ($this->_associationsList === null) {
237
            $this->_associationsList = AssociationReader::getAssociations($this);
238
        }
239
240 29
        $oldValue = empty($values['oldValue']) ? null : $values['oldValue'];
241 29
        $newValue = empty($values['newValue']) ? null : $values['newValue'];
242
243 29
        $association = $this->_associationsList[$property];
244 29
        if (!empty($association)) {
245 7
            $associatedProperty = $association['property'];
246 7
            switch ($association['association']) {
247 7
                case 'inverted':
248 5
                    $invertedGetMethod = 'get' . strtoupper(substr($associatedProperty, 0, 1)) . substr($associatedProperty, 1);
249 5
                    $invertedSetMethod = 'set' . strtoupper(substr($associatedProperty, 0, 1)) . substr($associatedProperty, 1);
250 5
                    if ($oldValue !== null && $oldValue->$invertedGetMethod() === $this) {
251 4
                        $oldValue->$invertedSetMethod(null);
252 4
                    }
253 5
                    if ($newValue !== null && $newValue->$invertedGetMethod() !== $this) {
254 5
                        $newValue->$invertedSetMethod($this);
255 5
                    }
256 5
                    break;
257
258 3
                case 'mapped':
259 3
                    $itemName = $association['itemName'];
260 3
                    $mappedGetMethod = 'get' . strtoupper(substr($associatedProperty, 0, 1)) . substr($associatedProperty, 1);
261 3
                    $mappedAddMethod = 'add' . strtoupper(substr($itemName, 0, 1)) . substr($itemName, 1);
262 3
                    $mappedRemoveMethod = 'remove' . strtoupper(substr($itemName, 0, 1)) . substr($itemName, 1);
263
264 3
                    if ($oldValue !== null && CollectionManager::collectionContains($this, $oldValue->$mappedGetMethod())) {
265 3
                        $oldValue->$mappedRemoveMethod($this);
266 3
                    }
267 3
                    if ($newValue !== null && !CollectionManager::collectionContains($this, $newValue->$mappedGetMethod())) {
268 3
                        $newValue->$mappedAddMethod($this);
269 3
                    }
270 3
                    break;
271 7
            }
272 7
        }
273 29
    }
274
275
    /**
276
     * Initializes the object according to its class specification and given arguments.
277
     *
278
     * @param array $properties The values to give to the properties.
279
     */
280 29
    protected function initializeProperties($properties = null)
281
    {
282 29
        $this->getPropertiesInfo();
283
284
        // Initialize the properties that were defined using the Initialize / InitializeObject annotations
285 29
        $initializeValueValidationEnabled = Configuration::isInitializeValuesValidationEnabled();
286
287 29
        $initialValues = AutoConstructReader::getPropertiesToInitialize($this);
288 29
        foreach ($initialValues as $propertyName => $value) {
289 29
            if ($initializeValueValidationEnabled) {
290 29
                $this->assertPropertyValue($propertyName, $value);
291 29
            }
292
293 29
            $this->$propertyName = $value;
294
295 29 View Code Duplication
            if (empty($this->_collectionsItemNames['byProperty'][$propertyName])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
296 29
                $this->updatePropertyAssociation($propertyName, array("oldValue" => null, "newValue" => $value));
297 29
            } else {
298 29
                foreach ($value as $newValue) {
299 8
                    $this->updatePropertyAssociation($propertyName, array("oldValue" => null, "newValue" => $newValue));
300 29
                }
301
            }
302 29
        }
303
304
        // Initialize the propeties using given arguments
305 29
        $neededArguments = AutoConstructReader::getConstructArguments($this);
306
307 29
        if ($neededArguments !== null && $properties !== null) {
308 3
            $numberOfNeededArguments = count($neededArguments);
309
310 3
            MethodCallManager::assertArgsNumber($numberOfNeededArguments, $properties);
311
312 3
            for ($i = 0; $i < $numberOfNeededArguments; $i++) {
313 3
                $property = $neededArguments[$i];
314 3
                $argument = $properties[$i];
315
316 3
                $this->assertPropertyValue($property, $argument);
317
318 2
                $this->$property = $argument;
319
320
                // Manage associations
321 2 View Code Duplication
                if (empty($this->_collectionsItemNames['byProperty'][$property])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
322 2
                    $this->updatePropertyAssociation($property, array("oldValue" => null, "newValue" => $argument));
323 2
                } else {
324
                    foreach ($argument as $value) {
325
                        $this->updatePropertyAssociation($property, array("oldValue" => null, "newValue" => $value));
326
                    }
327
                }
328 2
            }
329 2
        }
330 28
    }
331
332
    /**
333
     * Get every information needed from this class.
334
     */
335 29
    private function getPropertiesInfo()
336
    {
337 29
        if (!$this->_automatedBehaviorInitialized) {
338 29
            $this->_accessProperties = AccessReader::getAccessProperties($this);
339 29
            $this->_collectionsItemNames = CollectionsReader::getCollectionsItemNames($this);
340 29
            $this->_associationsList = AssociationReader::getAssociations($this);
341 29
            $this->_constraintsValidationEnabled = ConstraintsReader::isConstraintsValidationEnabled($this);
342
343 29
            $this->_automatedBehaviorInitialized = true;
344 29
        }
345 29
    }
346
347
    /**
348
     * Extract the info about the method called.
349
     *
350
     * @param string $name
351
     *
352
     * @return array
353
     */
354 26
    private function getMethodCallInfo($name)
355
    {
356
        // check that the called method is a valid method name
357
        // also get the call type and the property to access
358 26
        $callIsValid = preg_match("/(set|get|is|add|remove)([A-Z].*)/", $name, $pregMatches);
359 26
        if (!$callIsValid) {
360
            throw new \BadMethodCallException("Method $name does not exist.");
361
        }
362
363 26
        $method = $pregMatches[1];
364 26
        $property = strtolower(substr($pregMatches[2], 0, 1)) . substr($pregMatches[2], 1);
365 26
        $collectionProperties = null;
366 26
        if (in_array($method, array('add', 'remove'))) {
367 10
            $collectionProperties = $this->_collectionsItemNames['byItemName'][$property];
368 10
            $property = $collectionProperties['property'];
369 10
        }
370
371
        // check that the method is accepted by the targeted property
372
        if (
373 26
            empty($this->_accessProperties[$property])
374 24
            || !in_array($method, $this->_accessProperties[$property])
375 26
        ) {
376 2
            throw new \BadMethodCallException("Method $name does not exist.");
377
        }
378
379
        return array(
380 24
            'method' => $method,
381 24
            'property' => $property,
382
            'collectionProperties' => $collectionProperties
383 24
        );
384
    }
385
}
386