SaveAll::addRelated()   F
last analyzed

Complexity

Conditions 38
Paths 2133

Size

Total Lines 151

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 74
CRAP Score 43.8153

Importance

Changes 0
Metric Value
dl 0
loc 151
ccs 74
cts 88
cp 0.8409
rs 0
c 0
b 0
f 0
cc 38
nc 2133
nop 4
crap 43.8153

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Sigep\EloquentEnhancements\Traits;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7
use Illuminate\Database\Eloquent\Relations\HasMany;
8
use Illuminate\Database\Eloquent\Relations\MorphMany;
9
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
10
use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
13
trait SaveAll
14
{
15
    /**
16
     * @see Model::fill
17
     * @param array $attributes
18
     * @return mixed
19
     */
20
    abstract function fill(array $attributes);
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
21
22
    /**
23
     * @see Model::save
24
     * @param array $options
25
     * @return mixed
26
     */
27
    abstract function save(array $options = []);
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
28
29
    /**
30
     * Get the default foreign key name for the model.
31
     *
32
     * @return string
33
     */
34
    abstract function getForeignKey();
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
35
36
    /**
37
     * Checks if $options has a callable to do the validation.
38
     * If is provided, call the function and merge erros, if any
39
     * @param array $options
40
     * @param string $path
41
     * @return bool
42 22
     */
43
    private function __handleValidator(array $options, $path)
44 22
    {
45 22
        $modelName = get_class($this);
46 22
        $validator = null;
47
        $response = true;
48 22
49 1
        if (!empty($options[$modelName]['validator']) && is_callable($options[$modelName]['validator'])) {
50 22
            $validator = $options[$modelName]['validator'];
51 1
        } elseif (!empty($options['validator']) && is_callable($options['validator'])) {
52 1
            $validator = $options['validator'];
53
        }
54 22
55 2
        if ($validator) {
56 2
            $isValid = call_user_func($validator, $this);
57 2
            if ($isValid !== true) {
58 2
                $this->mergeErrors($isValid->toArray(), $path);
59 2
                $response = false;
60 2
            }
61
        }
62 22
63
        return $response;
64
    }
65
66
    /**
67
     * Checks if $options has restrictions about what can be filled and
68
     * filters $data
69
     * @param array $options
70
     * @param array $data
71 22
     */
72
    private function __handleFill(array $options, array $data)
73 22
    {
74 22
        $modelName = get_class($this);
75 1
        if (!empty($options[$modelName]['fillable'])) {
76 1
            $newData = [];
77 1
            foreach ($options[$modelName]['fillable'] as $field) {
78 1
                if (isset($data[$field])) {
79 1
                    $newData[$field] = $data[$field];
80 1
                }
81 1
            }
82 1
            $data = $newData;
83
        }
84 22
85 22
        $this->fill($data);
86
    }
87
88
    /**
89
     * create a new object and calls saveAll() method to save its relationships
90
     *
91
     * @param array $data
92
     * @param string $path used to control where put the error messages
93
     *
94
     * @return boolean
95 20
     */
96
    public function createAll(array $data = [], $options = [], $path = '')
97 20
    {
98 20
        $this->__handleFill($options, $data);
99
        $data = $this->checkBelongsTo($data, $options, $path);
100 20
101 1
        if ($this->errors()->count()) {
0 ignored issues
show
Bug introduced by
The method errors() does not exist on Sigep\EloquentEnhancements\Traits\SaveAll. Did you maybe mean mergeErrors()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
102
            return false;
103
        }
104 20
105 4
        if (!$this->__handleValidator($options, $path) || !$this->save()) {
106
            return false;
107
        }
108 20
109
        $data = $this->fillForeignKeyRecursively($data);
110 20
111
        return $this->saveAll($data, $options, true, $path);
112
    }
113
114
    /**
115
     * Update current record and create/update its related data
116
     * The related data must be array and the key is the name of the relationship
117
     * We support relationships from relationships too.
118
     *
119
     * @param  array $data
120
     * @param  boolean $skipUpdate if true, current model will not be updated
121
     * @return boolean
122 22
     */
123
    public function saveAll(array $data = [], array $options = [], $skipUpdate = false, $path = '')
124 22
    {
125 22
        $this->__handleFill($options, $data);
126
        $data = $this->checkBelongsTo($data, $options, $path); // @is really necessary?
127 22
128
        if ($this->errors()->count()) {
0 ignored issues
show
Bug introduced by
The method errors() does not exist on Sigep\EloquentEnhancements\Traits\SaveAll. Did you maybe mean mergeErrors()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
129
            return false;
130
        }
131 22
132 8
        if (!$skipUpdate) {
133 1
            if ($this->__handleValidator($options, $path) === false) {
134
                return false;
135 8
            }
136
            if (($this->save() === false) && ($this->isDirty())) {
0 ignored issues
show
Bug introduced by
It seems like isDirty() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
137 22
                return false;
138
            }
139
        }
140 22
141 20
        $relationships = $this->getRelationshipsFromData($data);
142 20
143
        // save relationships
144
        foreach ($relationships as $relationship => $values) {
145
            $currentPath = $path ? "{$path}." : '';
146 20
            $currentPath .= $relationship;
147 1
148
            // check allowed amount of related objects
149
            // @todo this is the best way? maybe this must be on validation rules...?
150 20
            if ($this->checkRelationshipLimit($relationship, $values, $currentPath) === false) {
151 1
                return false;
152 1
            }
153
154 20
            if ($this->shouldDetachModels($relationship, $values)) {
155 4
                $this->$relationship()->withTimestamps()->detach();
156
            }
157 21
158
            if ($this->addRelated($relationship, $values, $options, $currentPath) === false) {
159
                return false;
160 21
            }
161 10
        }
162 10
163
        // search for relationships that has limit and no data was send, to apply the minimum validation
164 10
        if (isset($this->relationshipsLimits)) {
165 1
            $relationshipsLimits = $this->relationshipsLimits;
0 ignored issues
show
Bug introduced by
The property relationshipsLimits does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
166 1
            $checkRelationships = array_diff(array_keys($relationshipsLimits), array_keys($relationships));
167
168 1
            foreach ($checkRelationships as $checkRelationship) {
169 1
                $currentPath = $path ? "{$path}." : '';
170
                $currentPath .= $checkRelationship;
171 10
172 10
                if (!$this->checkRelationshipLimit($checkRelationship, [], $currentPath)) {
173
                    return false;
174 21
                }
175
            }
176
        }
177
178
        return true;
179
    }
180
181
    /**
182
     * Put the id of current object as foreign key in all arrays inside $data
183
     * Util when a relationship of a relationship depends of the id from current
184
     * model as foreign key to.
185
     * To avoid problems, data must to have the foreign key with "auto" value.
186
     * Just in case that the records belongs, for any reason, to another object
187
     *
188 20
     * @param array $data changed data
189
     *
190 20
     * @return array
191
     */
192 20
    private function fillForeignKeyRecursively(array $data)
193 20
    {
194 17
        $foreign = $this->getForeignKey();
195 17
196 20
        foreach ($data as &$piece) {
197
            if (is_array($piece)) {
198 20
                $piece = $this->fillForeignKeyRecursively($piece);
199
            }
200
        }
201
202 20
        if (isset($data[$foreign]) && $data[$foreign] == 'auto' && $this->getKey()) {
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
203
            $data[$foreign] = $this->getKey();
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
204
        }
205
206
        return $data;
207
    }
208
209
    /**
210
 * Determines if sync() should be used to create records on belongsToMany relationships
211
 *
212
 * @param string $relationship name of relationship
213 20
 * @param array $data data to check
214
 *
215 20
 * @return bool
216 20
 */
217
    private function shouldUseSync($relationship, $data)
218 5
    {
219 5
        $relationship = $this->$relationship();
220 1
        if ($relationship instanceof BelongsToMany && count($data) === 1) {
221
            // check foreign key
222 4
            $foreignKey = last(explode('.', $relationship->getQualifiedRelatedPivotKeyName()));
223
            if (isset($data[$foreignKey]) && is_array($data[$foreignKey])) {
224 19
                return true;
225
            }
226
        }
227
228
        return false;
229
    }
230
231
    /**
232
     * Determines if sync() should be used to create records on belongsToMany relationships
233
     *
234
     * @param string $relationship name of relationship
235 20
     * @param array $data data to check
236 1
     *
237 20
     * @return bool
238 20
     */
239 9
    private function shouldDetachModels($relationship, $data)
240 8
    {
241 7
        $relationship = $this->$relationship();
242
        if ($relationship instanceof BelongsToMany && count($data) >= 1 && is_array($data)) {
243 2
            foreach ($data as $element) {
244 1
                if (!is_array($element) || empty($element) || empty($element['id'])) {
245
                    return false;
246
                }
247 12
            }
248
            return true;
249
        }
250
251
        return false;
252
    }
253
254
    /**
255
     * This method is specific to create objects that are related with the current model on a
256
     * belongsTo relationship.
257
     * Useful to create a record that belongs to another record that don't exists yet.
258
     * This method will remove from $data data relative to belongsTo elements
259
     *
260 22
     * @param array $data
261 22
     * @param array $options
262
     * @param string $path
263 22
     * @return array
264 21
     */
265
    private function checkBelongsTo($data, array $options = [], $path = '') {
266 21
        $relationships = $this->getRelationshipsFromData($data);
267 20
268
        foreach ($relationships as $relationship => $values) {
269
            /** @var Relation $relationshipObject */
270 3
            $relationshipObject = $this->$relationship();
271 3
272
            if ($relationshipObject instanceof BelongsTo === false) {
273 3
                continue;
274 3
            }
275 1
276 1
            $currentPath = $path ? "{$path}." : '';
277 2
            $currentPath .= $relationship;
278 2
279 2
            $foreignKey = $relationshipObject->getForeignKey();
280 1
            $keyOtherObject = $relationshipObject->getOwnerKey();
281 1
282 1
            if (!empty($values[$keyOtherObject])) {
283
                $this->$foreignKey = (int) $values[$keyOtherObject];
284
            } else {
285 3
                $object = $relationshipObject->getRelated();
286 22
                if (!$object->createAll($values, $options)) {
287
                    $this->mergeErrors($object->errors()->toArray(), $currentPath);
288 22
                } else {
289
                    $this->$foreignKey = $object->getKey();
290
                }
291
            }
292
293
            unset($data[$relationship]);
294
        }
295
296 20
        return $data;
297
    }
298 20
299 14
    /**
300 14
     * Get the specified limit for $relationship or false if not exists
301 14
     * @param $relationship name of the relationship
302 14
     * @return mixed
303
     */
304
    protected function getRelationshipLimit($relationship)
305 9
    {
306
        if (isset($this->relationshipsLimits[$relationship])) {
307
            return array_map (
308
                'intval',
309
                explode(':', $this->relationshipsLimits[$relationship])
310
            );
311
        }
312
313
        return false;
314
    }
315 20
316
    /**
317 20
     * Checks if amount of related objects are allowed
318 20
     * @param string $relationship relationship name
319 9
     * @param array $values
320
     * @param string $path
321
     * @return array modified $values
322 14
     */
323 1
    protected function checkRelationshipLimit($relationship, $values, $path)
324 1
    {
325 13
        $relationshipLimit = $this->getRelationshipLimit($relationship);
326 13
        if (!$relationshipLimit) {
327 13
            return $values;
328 13
        }
329
330
        if (count($values) === 1 && $this->shouldUseSync($relationship, $values)) {
331 13
            $sumRelationships = count(end($values));
332 1
        } else {
333
            $this->load($relationship);
0 ignored issues
show
Bug introduced by
It seems like load() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
334
            $currentRelationships = $this->$relationship->count();
335 12
            $newRelationships = 0;
336 12
            $removeRelationships = [];
337 12
            $relatedObjectKey = $this->$relationship()->getRelated()->getKeyName();
338 1
339 1
            // check if is associative
340
            if ($values && $values !== array_values($values)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
341
                return true; // @todo prevent this
342 12
            }
343 12
344 12
            foreach ($values as $key => $value) {
345 12
                $arrayIsEmpty = array_filter($value);
346 12
                if (empty($arrayIsEmpty)) {
347
                    unset($values[$key]);
348 12
                    continue;
349
                }
350
351 13
                if (!isset($value[$relatedObjectKey])) {
352 13
                    $newRelationships++;
353 1
                    $removeRelationships[] = $key;
354 1
                }
355
            }
356 13
357 1
            $sumRelationships = $currentRelationships + $newRelationships;
358 1
        }
359
360 13
        $this->errors();
0 ignored issues
show
Bug introduced by
The method errors() does not exist on Sigep\EloquentEnhancements\Traits\SaveAll. Did you maybe mean mergeErrors()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
361 1
        if ($sumRelationships < $relationshipLimit[0]) {
362
            $this->errors->add($path, 'validation.min', $relationshipLimit[0]);
0 ignored issues
show
Bug introduced by
The property errors does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
363
        }
364 13
365
        if ($sumRelationships > $relationshipLimit[1]) {
366
            $this->errors->add($path, 'validation.max', $relationshipLimit[1]);
367
        }
368
369
        if ($this->errors->has($path)) {
370
            return false;
371
        }
372
373 20
        return true;
374
    }
375 20
376
    /**
377
     * Add related data to the current model recursively
378 20
     * @param string $relationshipName
379 18
     * @param array $values
380 18
     * @return bool
381 18
     */
382 4
    public function addRelated($relationshipName, array $values, array $options = [], $path = '')
383
    {
384 17
        $relationship = $this->$relationshipName();
385
386 16
        // if is a numeric array, recursive calls to add multiple related
387
        if (ctype_digit(implode('', array_keys($values))) === true) {
388
            $position = 0;
389
            foreach ($values as $value) {
390 20
                if ($this->addRelated($relationshipName, $value, $options, $path . '.' . $position++) === false) {
391 20
                    return false;
392 1
                }
393
            }
394
395 20
            return true;
396 1
        }
397 1
398
        // if has not data, skip
399
        $arrayIsEmpty = array_filter($values);
400
        if (empty($arrayIsEmpty)) {
401 19
            return true;
402 12
        }
403 12
404
        if ($this->shouldUseSync($relationshipName, $values)) {
405
            $this->$relationshipName()->sync(end($values));
406 19
            return true;
407 1
        }
408 1
409 1
        // set foreign for hasMany relationships
410
        if ($relationship instanceof HasMany) {
411
            $values[last(explode('.', $relationship->getQualifiedForeignKeyName()))] = $this->getKey();
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
412 19
        }
413 7
414 7
        // if is MorphToMany, put other foreign and fill the type
415 7
        if ($relationship instanceof MorphMany) {
416
            $values[$relationship->getForeignKeyName()] = $this->getKey();
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
417
            $values[$relationship->getMorphType()] = get_class($this);
418 19
        }
419
420
        // if BelongsToMany, put current id in place
421 19
        if ($relationship instanceof BelongsToMany) {
422
            $values[last(explode('.', $relationship->getQualifiedForeignPivotKeyName()))] = $this->getKey();
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
423
            $belongsToManyOtherKey = last(explode('.', $relationship->getQualifiedRelatedPivotKeyName()));
424
        }
425 19
426 2
        // get target Model
427 2
        if ($relationship instanceof HasManyThrough) {
428
            $model = $relationship->getParent();
429
        } else {
430
            $model = $relationship->getRelated();
431
        }
432 2
433
        // if has ID, delete or update
434
        if (!empty($values[$model->getKeyName()]) && $relationship instanceof BelongsToMany === false) {
435
            $obj = $model->find($values[$model->getKeyName()]);
436 2
            if (!$obj) {
437
                return false; // @todo transport error
438
            }
439
440
            // delete or update?
441 2
            if (!empty($values['_delete'])) {
442
                return $obj->delete();
443
            }
444
445 19
            if (!$obj->saveAll($values, $options)) {
446 1
                $this->mergeErrors($obj->errors()->toArray(), $path);
447 1
                return true;
448
            }
449
450
            return true;
451 19
        }
452 3
453
        if (!empty($values['_delete']) && empty($values[$model->getKeyName()])) {
454
            // ignore
455
            return null;
456 3
        }
457 3
458 3
        // only BelongsToMany :)
459
        if (!empty($values['_delete']) && $relationship instanceof BelongsToMany) {
460
            $this->$relationshipName()->detach($values[last(explode('.', $relationship->getQualifiedRelatedPivotKeyName()))]);
461 3
            return true;
462
        }
463 3
464 2
465
        if ((isset($belongsToManyOtherKey) && empty($values[$belongsToManyOtherKey]))) {
466
            $obj = $relationship->getRelated();
467
            // if has conditions, fill the values
468 2
            // this helps to add static values in relationships using its conditions
469 2
            // @todo experimental
470
            foreach ($relationship->getQuery()->getQuery()->wheres as $where) {
471 3
                $column = last(explode('.', $where['column']));
472
                if (!empty($where['value']) && empty($values[$column])) {
473 19
                    $values[$column] = $where['value'];
474 12
                }
475 19
            }
476
477
            if (empty($values[$obj->getKeyName()])) {
478 7
                if (!$obj->createAll($values, $options)) {
479 4
                    $this->mergeErrors($obj->errors()->toArray(), $path);
480 4
                    return false;
481 1
                }
482 1
                $values[$belongsToManyOtherKey] = $obj->getKey();
483 4
            }
484 4
485 4
        }
486 4
487
        if ($relationship instanceof HasMany || $relationship instanceof MorphMany) {
488 4
            $relationshipObject = $relationship->getRelated();
489
        } elseif ($relationship instanceof BelongsToMany) {
490
            // if has a relationshipModel, use the model. Else, use attach
491 3
            // attach doesn't return nothing :(
492
            if (empty($this->relationshipsModels[$relationshipName])) {
0 ignored issues
show
Bug introduced by
The property relationshipsModels does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
493 3
                $field = last(explode('.', $relationship->getQualifiedRelatedPivotKeyName()));
494 2
                if (!isset($values[$field])) {
495 2
                    $field = 'id'; // default
496 2
                }
497 1
                $this->$relationshipName()->attach($values[$field]);
498 1
499 1
                return true;
500
            }
501 1
502
            $relationshipObjectName = $this->relationshipsModels[$relationshipName];
503 1
            $relationshipObject = new $relationshipObjectName;
504
            $relationshipObjectKeyName = $relationshipObject->getKeyName();
505
506 1
            if (!empty($values[$relationshipObjectKeyName])) {
507
                if (!empty($values[$belongsToManyOtherKey])) {
508
                    $relationshipObject = $relationshipObjectName::find($values[$relationshipObjectKeyName]);
509 2
                } else {
510
                    $relationshipObject = $relationship->getRelated()->find($values[$relationshipObjectKeyName]);
511
512
                    if (!empty($relationshipObject)) {
513 3
                        $this->$relationshipName()->withTimestamps()->attach($values[$relationshipObjectKeyName]);
514
                    }
515
                }
516
517 15
                if (!$relationshipObject) {
518 15
                    $relationshipObject = new $relationshipObjectName; // @todo check this out
519 4
                }
520 4
            }
521
        } elseif ($relationship instanceof HasManyThrough) {
522
            $relationshipObject = $model;
523 14
        }
524
525
        $useMethod = (empty($values[$relationshipObject->getKeyName()])) ? 'createAll' : 'saveAll';
0 ignored issues
show
Bug introduced by
The variable $relationshipObject does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
526
        if (!$relationshipObject->$useMethod($values, $options)) {
527
            $this->mergeErrors($relationshipObject->errors()->toArray(), $path);
528
            return false;
529
        }
530
531
        return true;
532 22
    }
533
534 22
    /**
535
     * get values that are array in $data
536 22
     * use this function to extract relationships from Input::all(), for example
537 22
     * @param  array $data
538 21
     * @return array
539 21
     */
540 22
    public function getRelationshipsFromData(array $data = [])
541
    {
542 22
        $relationships = [];
543
544
        foreach ($data as $key => $value) {
545
            if (is_array($value) && !is_numeric($key) && method_exists($this, $key)) {
546
                $relationships[$key] = $value;
547
            }
548
        }
549
550
        return $relationships;
551 4
    }
552
553 4
    /**
554 4
     * Merge $objErrors with $this->errors using $path
555 4
     *
556 4
     * @param array $objErrors
557 4
     * @param string $path
558 4
     */
559 4
    protected function mergeErrors(array $objErrors, $path)
560 4
    {
561
        $thisErrors = $this->errors();
0 ignored issues
show
Bug introduced by
The method errors() does not exist on Sigep\EloquentEnhancements\Traits\SaveAll. Did you maybe mean mergeErrors()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
562 4
        if ($path) {
563 4
            $path .= '.';
564 4
        }
565 4
        foreach ($objErrors as $field => $errors) {
566 4
            foreach ($errors as $error) {
567
                $thisErrors->add(
568
                    "{$path}{$field}",
569
                    $error
570
                );
571
            }
572
        }
573
        $this->setErrors($thisErrors);
0 ignored issues
show
Bug introduced by
It seems like setErrors() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
574
    }
575
}
576