Completed
Push — 5.2 ( 74d67b...fcc641 )
by Jarek
04:08
created

Mappable::pluckMapped()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 6
nc 2
nop 4
1
<?php
2
3
namespace Sofa\Eloquence;
4
5
use LogicException;
6
use Illuminate\Support\Arr;
7
use Sofa\Eloquence\Mappable\Hooks;
8
use Sofa\Hookable\Contracts\ArgumentBag;
9
use Illuminate\Contracts\Support\Arrayable;
10
use Illuminate\Database\Eloquent\Relations\HasOne;
11
use Illuminate\Database\Eloquent\Relations\MorphTo;
12
use Illuminate\Database\Eloquent\Relations\MorphOne;
13
use Illuminate\Database\Eloquent\Relations\Relation;
14
use Illuminate\Database\Eloquent\Relations\BelongsTo;
15
use Illuminate\Database\Eloquent\Model as EloquentModel;
16
17
/**
18
 * @property array $maps
19
 */
20
trait Mappable
21
{
22
    /**
23
     * Flat array representation of mapped attributes.
24
     *
25
     * @var array
26
     */
27
    protected static $mappedAttributes;
28
29
    /**
30
     * Related mapped objects to save along with the mappable instance.
31
     *
32
     * @var array
33
     */
34
    protected $targetsToSave = [];
35
36
    /**
37
     * Register hooks for the trait.
38
     *
39
     * @codeCoverageIgnore
40
     *
41
     * @return void
42
     */
43 View Code Duplication
    public static function bootMappable()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
44
    {
45
        $hooks = new Hooks;
46
47
        foreach ([
48
                'getAttribute',
49
                'setAttribute',
50
                'save',
51
                '__isset',
52
                '__unset',
53
                'queryHook',
54
            ] as $method) {
55
            static::hook($method, $hooks->{$method}());
56
        }
57
    }
58
59
    /**
60
     * Custom query handler for querying mapped attributes.
61
     *
62
     * @param  \Sofa\Eloquence\Builder $query
63
     * @param  string $method
64
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
65
     * @return mixed
66
     */
67
    protected function mappedQuery(Builder $query, $method, ArgumentBag $args)
68
    {
69
        $mapping = $this->getMappingForAttribute($args->get('column'));
70
71
        if ($this->relationMapping($mapping)) {
72
            return $this->mappedRelationQuery($query, $method, $args, $mapping);
73
        }
74
75
        $args->set('column', $mapping);
76
77
        return $query->callParent($method, $args->all());
78
    }
79
80
    /**
81
     * Adjust mapped columns for select statement.
82
     *
83
     * @param  \Sofa\Eloquence\Builder $query
84
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
85
     * @return void
86
     */
87
    protected function mappedSelect(Builder $query, ArgumentBag $args)
88
    {
89
        $columns = $args->get('columns');
90
91
        foreach ($columns as $key => $column) {
92
            list($column) = $this->extractColumnAlias($column);
0 ignored issues
show
Bug introduced by
It seems like extractColumnAlias() 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...
93
94
            // Each mapped column will be selected appropriately. If it's alias
95
            // then prefix it with current table and use original field name
96
            // otherwise join required mapped tables and select the field.
97
            if ($this->hasMapping($column)) {
98
                $mapping = $this->getMappingForAttribute($column);
99
100
                if ($this->relationMapping($mapping)) {
101
                    list($target, $mapped) = $this->parseMappedColumn($mapping);
0 ignored issues
show
Bug introduced by
It seems like parseMappedColumn() 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...
102
103
                    $table = $this->joinMapped($query, $target);
104
                } else {
105
                    list($table, $mapped) = [$this->getTable(), $mapping];
0 ignored issues
show
Bug introduced by
It seems like getTable() 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...
106
                }
107
108
                $columns[$key] = "{$table}.{$mapped}";
109
110
            // For non mapped columns present on this table we will simply
111
            // add the prefix, in order to avoid any column collisions,
112
            // that are likely to happen when we are joining tables.
113
            } elseif ($this->hasColumn($column)) {
0 ignored issues
show
Bug introduced by
It seems like hasColumn() 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...
114
                $columns[$key] = "{$this->getTable()}.{$column}";
0 ignored issues
show
Bug introduced by
It seems like getTable() 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...
115
            }
116
        }
117
118
        $args->set('columns', $columns);
119
    }
120
121
    /**
122
     * Handle querying relational mappings.
123
     *
124
     * @param  \Sofa\Eloquence\Builder $query
125
     * @param  string $method
126
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
127
     * @param  string $mapping
128
     * @return mixed
129
     */
130
    protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapping)
131
    {
132
        list($target, $column) = $this->parseMappedColumn($mapping);
0 ignored issues
show
Bug introduced by
It seems like parseMappedColumn() 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...
133
134 View Code Duplication
        if (in_array($method, ['pluck', 'value', 'aggregate', 'orderBy', 'lists'])) {
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...
135
            return $this->mappedJoinQuery($query, $method, $args, $target, $column);
136
        }
137
138
        return $this->mappedHasQuery($query, $method, $args, $target, $column);
139
    }
140
141
    /**
142
     * Join mapped table(s) in order to call given method.
143
     *
144
     * @param  \Sofa\Eloquence\Builder $query
145
     * @param  string $method
146
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
147
     * @param  string $target
148
     * @param  string $column
149
     * @return mixed
150
     */
151
    protected function mappedJoinQuery($query, $method, ArgumentBag $args, $target, $column)
152
    {
153
        $table = $this->joinMapped($query, $target);
154
155
        // For aggregates we need the actual function name
156
        // so it can be called directly on the builder.
157
        $method = $args->get('function') ?: $method;
158
159
        return (in_array($method, ['orderBy', 'lists', 'pluck']))
160
            ? $this->{"{$method}Mapped"}($query, $args, $table, $column, $target)
161
            : $this->mappedSingleResult($query, $method, "{$table}.{$column}");
162
    }
163
164
    /**
165
     * Order query by mapped attribute.
166
     *
167
     * @param  \Sofa\Eloquence\Builder $query
168
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
169
     * @param  string $table
170
     * @param  string $column
171
     * @param  string $target
172
     * @return \Sofa\Eloquence\Builder
173
     */
174
    protected function orderByMapped(Builder $query, ArgumentBag $args, $table, $column, $target)
175
    {
176
        $query->with($target)->getQuery()->orderBy("{$table}.{$column}", $args->get('direction'));
177
178
        return $query;
179
    }
180
181
    protected function listsMapped(Builder $query, ArgumentBag $args, $table, $column)
182
    {
183
        return $this->pluckMapped($query, $args, $table, $column);
184
    }
185
186
    /**
187
     * Get an array with the values of given mapped attribute.
188
     *
189
     * @param  \Sofa\Eloquence\Builder $query
190
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
191
     * @param  string $table
192
     * @param  string $column
193
     * @return array
194
     */
195
    protected function pluckMapped(Builder $query, ArgumentBag $args, $table, $column)
196
    {
197
        $query->select("{$table}.{$column}");
198
199
        if (!is_null($args->get('key'))) {
200
            $this->mappedSelectListsKey($query, $args->get('key'));
201
        }
202
203
        $args->set('column', $column);
204
205
        return $query->callParent('pluck', $args->all());
206
    }
207
208
    /**
209
     * Add select clause for key of the list array.
210
     *
211
     * @param  \Sofa\Eloquence\Builder $query
212
     * @param  string $key
213
     * @return \Sofa\Eloquence\Builder
214
     */
215
    protected function mappedSelectListsKey(Builder $query, $key)
216
    {
217
        if ($this->hasColumn($key)) {
0 ignored issues
show
Bug introduced by
It seems like hasColumn() 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...
218
            return $query->addSelect($this->getTable() . '.' . $key);
0 ignored issues
show
Bug introduced by
It seems like getTable() 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...
Bug introduced by
The method addSelect() does not exist on Sofa\Eloquence\Builder. Did you maybe mean select()?

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...
219
        }
220
221
        return $query->addSelect($key);
0 ignored issues
show
Bug introduced by
The method addSelect() does not exist on Sofa\Eloquence\Builder. Did you maybe mean select()?

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...
222
    }
223
224
    /**
225
     * Join mapped table(s).
226
     *
227
     * @param  \Sofa\Eloquence\Builder $query
228
     * @param  string $target
229
     * @return string
230
     */
231
    protected function joinMapped(Builder $query, $target)
232
    {
233
        $query->prefixColumnsForJoin();
234
235
        $parent = $this;
236
237
        foreach (explode('.', $target) as $segment) {
238
            list($table, $parent) = $this->joinSegment($query, $segment, $parent);
239
        }
240
241
        return $table;
0 ignored issues
show
Bug introduced by
The variable $table 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...
242
    }
243
244
    /**
245
     * Join relation's table accordingly.
246
     *
247
     * @param  \Sofa\Eloquence\Builder $query
248
     * @param  string $segment
249
     * @param  \Illuminate\Database\Eloquent\Model $parent
250
     * @return array
251
     */
252
    protected function joinSegment(Builder $query, $segment, EloquentModel $parent)
253
    {
254
        $relation = $parent->{$segment}();
255
        $related  = $relation->getRelated();
256
        $table    = $related->getTable();
257
258
        // If the table has been already joined let's skip it. Otherwise we will left join
259
        // it in order to allow using some query methods on mapped columns. Polymorphic
260
        // relations require also additional constraints, so let's handle it as well.
261
        if (!$this->alreadyJoined($query, $table)) {
262
            list($fk, $pk) = $this->getJoinKeys($relation);
263
264
            $query->leftJoin($table, function ($join) use ($fk, $pk, $relation, $parent, $related) {
0 ignored issues
show
Bug introduced by
The call to leftJoin() misses some required arguments starting with $operator.
Loading history...
265
                $join->on($fk, '=', $pk);
266
267
                if ($relation instanceof MorphOne || $relation instanceof MorphTo) {
268
                    $morphClass = ($relation instanceof MorphOne)
269
                        ? $parent->getMorphClass()
270
                        : $related->getMorphClass();
271
272
                    $join->where($relation->getMorphType(), '=', $morphClass);
273
                }
274
            });
275
        }
276
277
        return [$table, $related];
278
    }
279
280
    /**
281
     * Determine whether given table has been already joined.
282
     *
283
     * @param  \Sofa\Eloquence\Builder $query
284
     * @param  string  $table
285
     * @return boolean
286
     */
287
    protected function alreadyJoined(Builder $query, $table)
288
    {
289
        $joined = Arr::pluck((array) $query->getQuery()->joins, 'table');
290
291
        return in_array($table, $joined);
292
    }
293
294
    /**
295
     * Get the keys from relation in order to join the table.
296
     *
297
     * @param  \Illuminate\Database\Eloquent\Relations\Relation $relation
298
     * @return array
299
     *
300
     * @throws \LogicException
301
     */
302
    protected function getJoinKeys(Relation $relation)
303
    {
304
        if ($relation instanceof HasOne || $relation instanceof MorphOne) {
305
            return [$relation->getForeignKey(), $relation->getQualifiedParentKeyName()];
306
        }
307
308
        if ($relation instanceof BelongsTo && !$relation instanceof MorphTo) {
309
            return [$relation->getQualifiedForeignKey(), $relation->getQualifiedOtherKeyName()];
310
        }
311
312
        $class = get_class($relation);
313
314
        throw new LogicException(
315
            "Only HasOne, MorphOne and BelongsTo mappings can be queried. {$class} given."
316
        );
317
    }
318
319
    /**
320
     * Get single value result from the mapped attribute.
321
     *
322
     * @param  \Sofa\Eloquence\Builder $query
323
     * @param  string $method
324
     * @param  string $qualifiedColumn
325
     * @return mixed
326
     */
327
    protected function mappedSingleResult(Builder $query, $method, $qualifiedColumn)
328
    {
329
        return $query->getQuery()->select("{$qualifiedColumn}")->{$method}("{$qualifiedColumn}");
330
    }
331
332
    /**
333
     * Add whereHas subquery on the mapped attribute relation.
334
     *
335
     * @param  \Sofa\Eloquence\Builder $query
336
     * @param  string $method
337
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
338
     * @param  string $target
339
     * @param  string $column
340
     * @return \Sofa\Eloquence\Builder
341
     */
342
    protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $target, $column)
343
    {
344
        $boolean = $this->getMappedBoolean($args);
345
346
        $operator = $this->getMappedOperator($method, $args);
347
348
        $args->set('column', $column);
349
350
        return $query
351
            ->has($target, $operator, 1, $boolean, $this->getMappedWhereConstraint($method, $args))
352
            ->with($target);
353
    }
354
355
    /**
356
     * Get the relation constraint closure.
357
     *
358
     * @param  string $method
359
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
360
     * @return \Closure
361
     */
362
    protected function getMappedWhereConstraint($method, ArgumentBag $args)
363
    {
364
        return function ($query) use ($method, $args) {
365
            call_user_func_array([$query, $method], $args->all());
366
        };
367
    }
368
369
    /**
370
     * Get boolean called on the original method and set it to default.
371
     *
372
     * @param  \Sofa\EloquenceArgumentBag $args
373
     * @return string
374
     */
375
    protected function getMappedBoolean(ArgumentBag $args)
376
    {
377
        $boolean = $args->get('boolean');
378
379
        $args->set('boolean', 'and');
380
381
        return $boolean;
382
    }
383
384
    /**
385
     * Determine the operator for count relation query and set 'not' appropriately.
386
     *
387
     * @param  string $method
388
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
389
     * @return string
390
     */
391
    protected function getMappedOperator($method, ArgumentBag $args)
392
    {
393
        if ($not = $args->get('not')) {
394
            $args->set('not', false);
395
        }
396
397
        if ($null = $this->isWhereNull($method, $args)) {
0 ignored issues
show
Bug introduced by
It seems like isWhereNull() 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...
398
            $args->set('not', true);
399
        }
400
401
        return ($not ^ $null) ? '<' : '>=';
402
    }
403
404
    /**
405
     * Get the mapping key.
406
     *
407
     * @param  string $key
408
     * @return string|null
409
     */
410
    public function getMappingForAttribute($key)
411
    {
412
        if ($this->hasMapping($key)) {
413
            return static::$mappedAttributes[$key];
414
        }
415
    }
416
417
    /**
418
     * Determine whether the mapping points to relation.
419
     *
420
     * @param  string $mapping
421
     * @return boolean
422
     */
423
    protected function relationMapping($mapping)
424
    {
425
        return strpos($mapping, '.') !== false;
426
    }
427
428
    /**
429
     * Determine whether a mapping exists for an attribute.
430
     *
431
     * @param  string $key
432
     * @return boolean
433
     */
434
    public function hasMapping($key)
435
    {
436
        if (is_null(static::$mappedAttributes)) {
437
            $this->parseMappings();
438
        }
439
440
        return array_key_exists((string) $key, static::$mappedAttributes);
441
    }
442
443
    /**
444
     * Parse defined mappings into flat array.
445
     *
446
     * @return void
447
     */
448
    protected function parseMappings()
449
    {
450
        static::$mappedAttributes = [];
451
452
        foreach ($this->getMaps() as $attribute => $mapping) {
453
            if (is_array($mapping)) {
454
                $this->parseImplicitMapping($mapping, $attribute);
455
            } else {
456
                static::$mappedAttributes[$attribute] = $mapping;
457
            }
458
        }
459
    }
460
461
    /**
462
     * Parse implicit mappings.
463
     *
464
     * @param  array  $attributes
465
     * @param  string $target
466
     * @return void
467
     */
468
    protected function parseImplicitMapping($attributes, $target)
469
    {
470
        foreach ($attributes as $attribute) {
471
            static::$mappedAttributes[$attribute] = "{$target}.{$attribute}";
472
        }
473
    }
474
475
    /**
476
     * Map an attribute to a value.
477
     *
478
     * @param  string $key
479
     * @return mixed
480
     */
481
    protected function mapAttribute($key)
482
    {
483
        $segments = explode('.', $this->getMappingForAttribute($key));
484
485
        return $this->getTarget($this, $segments);
486
    }
487
488
    /**
489
     * Get mapped value.
490
     *
491
     * @param  \Illuminate\Database\Eloquent\Model $target
492
     * @param  array  $segments
493
     * @return mixed
494
     */
495
    protected function getTarget($target, array $segments)
496
    {
497
        foreach ($segments as $segment) {
498
            if (!$target) {
499
                return;
500
            }
501
502
            $target = $target->{$segment};
503
        }
504
505
        return $target;
506
    }
507
508
    /**
509
     * Set value of a mapped attribute.
510
     *
511
     * @param string $key
512
     * @param mixed  $value
513
     */
514 View Code Duplication
    protected function setMappedAttribute($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
515
    {
516
        $segments = explode('.', $this->getMappingForAttribute($key));
517
518
        $attribute = array_pop($segments);
519
520
        if ($target = $this->getTarget($this, $segments)) {
521
            $this->addTargetToSave($target);
522
523
            $target->{$attribute} = $value;
524
        }
525
    }
526
527
    /**
528
     * Flag mapped model to be saved along with this model.
529
     *
530
     * @param \Illuminate\Database\Eloquent\Model $target
531
     */
532
    protected function addTargetToSave($target)
533
    {
534
        if ($this !== $target) {
535
            $this->targetsToSave[] = $target;
536
        }
537
    }
538
539
    /**
540
     * Save mapped relations.
541
     *
542
     * @return void
543
     */
544
    protected function saveMapped()
545
    {
546
        foreach (array_unique($this->targetsToSave) as $target) {
547
            $target->save();
548
        }
549
550
        $this->targetsToSave = [];
551
    }
552
553
    /**
554
     * Unset mapped attribute.
555
     *
556
     * @param  string $key
557
     * @return void
558
     */
559 View Code Duplication
    protected function forget($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
560
    {
561
        $mapping = $this->getMappingForAttribute($key);
562
563
        list($target, $attribute) = $this->parseMappedColumn($mapping);
0 ignored issues
show
Bug introduced by
It seems like parseMappedColumn() 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...
564
565
        $target = $target ? $this->getTarget($this, explode('.', $target)) : $this;
566
567
        unset($target->{$attribute});
568
    }
569
570
    /**
571
     * @codeCoverageIgnore
572
     *
573
     * @inheritdoc
574
     */
575
    protected function mutateAttributeForArray($key, $value)
576
    {
577
        if ($this->hasMapping($key)) {
578
            $value = $this->mapAttribute($key);
579
580
            return $value instanceof Arrayable ? $value->toArray() : $value;
581
        }
582
583
        return parent::mutateAttributeForArray($key, $value);
584
    }
585
586
    /**
587
     * Get the array of attribute mappings.
588
     *
589
     * @return array
590
     */
591
    public function getMaps()
592
    {
593
        return (property_exists($this, 'maps')) ? $this->maps : [];
594
    }
595
}
596