Completed
Pull Request — 5.2 (#51)
by
unknown
03:17 queued 12s
created

Mappable::isMapping()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
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
                'toArray'
55
            ] as $method) {
56
            static::hook($method, $hooks->{$method}());
57
        }
58
    }
59
60
    /**
61
     * Custom query handler for querying mapped attributes.
62
     *
63
     * @param  \Sofa\Eloquence\Builder $query
64
     * @param  string $method
65
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
66
     * @return mixed
67
     */
68
    protected function mappedQuery(Builder $query, $method, ArgumentBag $args)
69
    {
70
        $mapping = $this->getMappingForAttribute($args->get('column'));
71
72
        if ($this->relationMapping($mapping)) {
73
            return $this->mappedRelationQuery($query, $method, $args, $mapping);
74
        }
75
76
        $args->set('column', $mapping);
77
78
        return $query->callParent($method, $args->all());
79
    }
80
81
    /**
82
     * Adjust mapped columns for select statement.
83
     *
84
     * @param  \Sofa\Eloquence\Builder $query
85
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
86
     * @return void
87
     */
88
    protected function mappedSelect(Builder $query, ArgumentBag $args)
89
    {
90
        $columns = $args->get('columns');
91
92
        foreach ($columns as $key => $column) {
93
            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...
94
95
            // Each mapped column will be selected appropriately. If it's alias
96
            // then prefix it with current table and use original field name
97
            // otherwise join required mapped tables and select the field.
98
            if ($this->hasMapping($column)) {
99
                $mapping = $this->getMappingForAttribute($column);
100
101
                if ($this->relationMapping($mapping)) {
102
                    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...
103
104
                    $table = $this->joinMapped($query, $target);
105
                } else {
106
                    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...
107
                }
108
109
                $columns[$key] = "{$table}.{$mapped}";
110
111
            // For non mapped columns present on this table we will simply
112
            // add the prefix, in order to avoid any column collisions,
113
            // that are likely to happen when we are joining tables.
114
            } 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...
115
                $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...
116
            }
117
        }
118
119
        $args->set('columns', $columns);
120
    }
121
122
    /**
123
     * Handle querying relational mappings.
124
     *
125
     * @param  \Sofa\Eloquence\Builder $query
126
     * @param  string $method
127
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
128
     * @param  string $mapping
129
     * @return mixed
130
     */
131
    protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapping)
132
    {
133
        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...
134
135 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...
136
            return $this->mappedJoinQuery($query, $method, $args, $target, $column);
137
        }
138
139
        return $this->mappedHasQuery($query, $method, $args, $target, $column);
140
    }
141
142
    /**
143
     * Join mapped table(s) in order to call given method.
144
     *
145
     * @param  \Sofa\Eloquence\Builder $query
146
     * @param  string $method
147
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
148
     * @param  string $target
149
     * @param  string $column
150
     * @return mixed
151
     */
152
    protected function mappedJoinQuery($query, $method, ArgumentBag $args, $target, $column)
153
    {
154
        $table = $this->joinMapped($query, $target);
155
156
        // For aggregates we need the actual function name
157
        // so it can be called directly on the builder.
158
        $method = $args->get('function') ?: $method;
159
160
        return (in_array($method, ['orderBy', 'lists', 'pluck']))
161
            ? $this->{"{$method}Mapped"}($query, $args, $table, $column, $target)
162
            : $this->mappedSingleResult($query, $method, "{$table}.{$column}");
163
    }
164
165
    /**
166
     * Order query by mapped attribute.
167
     *
168
     * @param  \Sofa\Eloquence\Builder $query
169
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
170
     * @param  string $table
171
     * @param  string $column
172
     * @param  string $target
173
     * @return \Sofa\Eloquence\Builder
174
     */
175
    protected function orderByMapped(Builder $query, ArgumentBag $args, $table, $column, $target)
176
    {
177
        $query->with($target)->getQuery()->orderBy("{$table}.{$column}", $args->get('direction'));
178
179
        return $query;
180
    }
181
182
    protected function listsMapped(Builder $query, ArgumentBag $args, $table, $column)
183
    {
184
        return $this->pluckMapped($query, $args, $table, $column);
185
    }
186
187
    /**
188
     * Get an array with the values of given mapped attribute.
189
     *
190
     * @param  \Sofa\Eloquence\Builder $query
191
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
192
     * @param  string $table
193
     * @param  string $column
194
     * @return array
195
     */
196
    protected function pluckMapped(Builder $query, ArgumentBag $args, $table, $column)
197
    {
198
        $query->select("{$table}.{$column}");
199
200
        if (!is_null($args->get('key'))) {
201
            $this->mappedSelectListsKey($query, $args->get('key'));
202
        }
203
204
        $args->set('column', $column);
205
206
        return $query->callParent('pluck', $args->all());
207
    }
208
209
    /**
210
     * Add select clause for key of the list array.
211
     *
212
     * @param  \Sofa\Eloquence\Builder $query
213
     * @param  string $key
214
     * @return \Sofa\Eloquence\Builder
215
     */
216
    protected function mappedSelectListsKey(Builder $query, $key)
217
    {
218
        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...
219
            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...
220
        }
221
222
        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...
223
    }
224
225
    /**
226
     * Join mapped table(s).
227
     *
228
     * @param  \Sofa\Eloquence\Builder $query
229
     * @param  string $target
230
     * @return string
231
     */
232
    protected function joinMapped(Builder $query, $target)
233
    {
234
        $query->prefixColumnsForJoin();
235
236
        $parent = $this;
237
238
        foreach (explode('.', $target) as $segment) {
239
            list($table, $parent) = $this->joinSegment($query, $segment, $parent);
240
        }
241
242
        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...
243
    }
244
245
    /**
246
     * Join relation's table accordingly.
247
     *
248
     * @param  \Sofa\Eloquence\Builder $query
249
     * @param  string $segment
250
     * @param  \Illuminate\Database\Eloquent\Model $parent
251
     * @return array
252
     */
253
    protected function joinSegment(Builder $query, $segment, EloquentModel $parent)
254
    {
255
        $relation = $parent->{$segment}();
256
        $related  = $relation->getRelated();
257
        $table    = $related->getTable();
258
259
        // If the table has been already joined let's skip it. Otherwise we will left join
260
        // it in order to allow using some query methods on mapped columns. Polymorphic
261
        // relations require also additional constraints, so let's handle it as well.
262
        if (!$this->alreadyJoined($query, $table)) {
263
            list($fk, $pk) = $this->getJoinKeys($relation);
264
265
            $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...
266
                $join->on($fk, '=', $pk);
267
268
                if ($relation instanceof MorphOne || $relation instanceof MorphTo) {
269
                    $morphClass = ($relation instanceof MorphOne)
270
                        ? $parent->getMorphClass()
271
                        : $related->getMorphClass();
272
273
                    $join->where($relation->getMorphType(), '=', $morphClass);
274
                }
275
            });
276
        }
277
278
        return [$table, $related];
279
    }
280
281
    /**
282
     * Determine whether given table has been already joined.
283
     *
284
     * @param  \Sofa\Eloquence\Builder $query
285
     * @param  string  $table
286
     * @return boolean
287
     */
288
    protected function alreadyJoined(Builder $query, $table)
289
    {
290
        $joined = Arr::pluck((array) $query->getQuery()->joins, 'table');
291
292
        return in_array($table, $joined);
293
    }
294
295
    /**
296
     * Get the keys from relation in order to join the table.
297
     *
298
     * @param  \Illuminate\Database\Eloquent\Relations\Relation $relation
299
     * @return array
300
     *
301
     * @throws \LogicException
302
     */
303
    protected function getJoinKeys(Relation $relation)
304
    {
305
        if ($relation instanceof HasOne || $relation instanceof MorphOne) {
306
            return [$relation->getForeignKey(), $relation->getQualifiedParentKeyName()];
307
        }
308
309
        if ($relation instanceof BelongsTo && !$relation instanceof MorphTo) {
310
            return [$relation->getQualifiedForeignKey(), $relation->getQualifiedOtherKeyName()];
311
        }
312
313
        $class = get_class($relation);
314
315
        throw new LogicException(
316
            "Only HasOne, MorphOne and BelongsTo mappings can be queried. {$class} given."
317
        );
318
    }
319
320
    /**
321
     * Get single value result from the mapped attribute.
322
     *
323
     * @param  \Sofa\Eloquence\Builder $query
324
     * @param  string $method
325
     * @param  string $qualifiedColumn
326
     * @return mixed
327
     */
328
    protected function mappedSingleResult(Builder $query, $method, $qualifiedColumn)
329
    {
330
        return $query->getQuery()->select("{$qualifiedColumn}")->{$method}("{$qualifiedColumn}");
331
    }
332
333
    /**
334
     * Add whereHas subquery on the mapped attribute relation.
335
     *
336
     * @param  \Sofa\Eloquence\Builder $query
337
     * @param  string $method
338
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
339
     * @param  string $target
340
     * @param  string $column
341
     * @return \Sofa\Eloquence\Builder
342
     */
343
    protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $target, $column)
344
    {
345
        $boolean = $this->getMappedBoolean($args);
346
347
        $operator = $this->getMappedOperator($method, $args);
348
349
        $args->set('column', $column);
350
351
        return $query
352
            ->has($target, $operator, 1, $boolean, $this->getMappedWhereConstraint($method, $args))
353
            ->with($target);
354
    }
355
356
    /**
357
     * Get the relation constraint closure.
358
     *
359
     * @param  string $method
360
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
361
     * @return \Closure
362
     */
363
    protected function getMappedWhereConstraint($method, ArgumentBag $args)
364
    {
365
        return function ($query) use ($method, $args) {
366
            call_user_func_array([$query, $method], $args->all());
367
        };
368
    }
369
370
    /**
371
     * Get boolean called on the original method and set it to default.
372
     *
373
     * @param  \Sofa\EloquenceArgumentBag $args
374
     * @return string
375
     */
376
    protected function getMappedBoolean(ArgumentBag $args)
377
    {
378
        $boolean = $args->get('boolean');
379
380
        $args->set('boolean', 'and');
381
382
        return $boolean;
383
    }
384
385
    /**
386
     * Determine the operator for count relation query and set 'not' appropriately.
387
     *
388
     * @param  string $method
389
     * @param  \Sofa\Hookable\Contracts\ArgumentBag $args
390
     * @return string
391
     */
392
    protected function getMappedOperator($method, ArgumentBag $args)
393
    {
394
        if ($not = $args->get('not')) {
395
            $args->set('not', false);
396
        }
397
398
        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...
399
            $args->set('not', true);
400
        }
401
402
        return ($not ^ $null) ? '<' : '>=';
403
    }
404
405
    /**
406
     * Get the mapping key.
407
     *
408
     * @param  string $key
409
     * @return string|null
410
     */
411
    public function getMappingForAttribute($key)
412
    {
413
        if ($this->hasMapping($key)) {
414
            return static::$mappedAttributes[$key];
415
        }
416
    }
417
418
    /**
419
     * Determine whether the mapping points to relation.
420
     *
421
     * @param  string $mapping
422
     * @return boolean
423
     */
424
    protected function relationMapping($mapping)
425
    {
426
        return strpos($mapping, '.') !== false;
427
    }
428
429
    /**
430
     * Determine whether a mapping exists for an attribute.
431
     *
432
     * @param  string $key
433
     * @return boolean
434
     */
435
    public function hasMapping($key)
436
    {
437
        if (is_null(static::$mappedAttributes)) {
438
            $this->parseMappings();
439
        }
440
441
        return array_key_exists((string) $key, static::$mappedAttributes);
442
    }
443
444
    /**
445
     * Parse defined mappings into flat array.
446
     *
447
     * @return void
448
     */
449
    protected function parseMappings()
450
    {
451
        static::$mappedAttributes = [];
452
453
        foreach ($this->getMaps() as $attribute => $mapping) {
454
            if (is_array($mapping)) {
455
                $this->parseImplicitMapping($mapping, $attribute);
456
            } else {
457
                static::$mappedAttributes[$attribute] = $mapping;
458
            }
459
        }
460
    }
461
462
    /**
463
     * Parse implicit mappings.
464
     *
465
     * @param  array  $attributes
466
     * @param  string $target
467
     * @return void
468
     */
469
    protected function parseImplicitMapping($attributes, $target)
470
    {
471
        foreach ($attributes as $attribute) {
472
            static::$mappedAttributes[$attribute] = "{$target}.{$attribute}";
473
        }
474
    }
475
476
    /**
477
     * Map an attribute to a value.
478
     *
479
     * @param  string $key
480
     * @return mixed
481
     */
482
    protected function mapAttribute($key)
483
    {
484
        $segments = explode('.', $this->getMappingForAttribute($key));
485
486
        return $this->getTarget($this, $segments);
487
    }
488
489
    /**
490
     * Determine whether a column is mapped.
491
     *
492
     * @param $column
493
     * @return string
494
     */
495
    public function isMapping($column)
496
    {
497
        if (is_null(static::$mappedAttributes)) {
498
            $this->parseMappings();
499
        }
500
        return array_search($column, static::$mappedAttributes);
501
    }
502
    /**
503
     * Get mapped value.
504
     *
505
     * @param  \Illuminate\Database\Eloquent\Model $target
506
     * @param  array  $segments
507
     * @return mixed
508
     */
509
    protected function getTarget($target, array $segments)
510
    {
511
        foreach ($segments as $segment) {
512
            if (!$target) {
513
                return;
514
            }
515
516
            $target = $target->{$segment};
517
        }
518
519
        return $target;
520
    }
521
522
    /**
523
     * Set value of a mapped attribute.
524
     *
525
     * @param string $key
526
     * @param mixed  $value
527
     */
528 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...
529
    {
530
        $segments = explode('.', $this->getMappingForAttribute($key));
531
532
        $attribute = array_pop($segments);
533
534
        if ($target = $this->getTarget($this, $segments)) {
535
            $this->addTargetToSave($target);
536
537
            $target->{$attribute} = $value;
538
        }
539
    }
540
541
    /**
542
     * Flag mapped model to be saved along with this model.
543
     *
544
     * @param \Illuminate\Database\Eloquent\Model $target
545
     */
546
    protected function addTargetToSave($target)
547
    {
548
        if ($this !== $target) {
549
            $this->targetsToSave[] = $target;
550
        }
551
    }
552
553
    /**
554
     * Save mapped relations.
555
     *
556
     * @return void
557
     */
558
    protected function saveMapped()
559
    {
560
        foreach (array_unique($this->targetsToSave) as $target) {
561
            $target->save();
562
        }
563
564
        $this->targetsToSave = [];
565
    }
566
567
    /**
568
     * Unset mapped attribute.
569
     *
570
     * @param  string $key
571
     * @return void
572
     */
573 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...
574
    {
575
        $mapping = $this->getMappingForAttribute($key);
576
577
        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...
578
579
        $target = $target ? $this->getTarget($this, explode('.', $target)) : $this;
580
581
        unset($target->{$attribute});
582
    }
583
584
    /**
585
     * @codeCoverageIgnore
586
     *
587
     * @inheritdoc
588
     */
589
    protected function mutateAttributeForArray($key, $value)
590
    {
591
        if ($this->hasMapping($key)) {
592
            $value = $this->mapAttribute($key);
593
594
            return $value instanceof Arrayable ? $value->toArray() : $value;
595
        }
596
597
        return parent::mutateAttributeForArray($key, $value);
598
    }
599
600
    /**
601
     * Get the array of attribute mappings.
602
     *
603
     * @return array
604
     */
605
    public function getMaps()
606
    {
607
        return (property_exists($this, 'maps')) ? $this->maps : [];
608
    }
609
}
610