Completed
Branch master (411345)
by Rémi
11:20
created

BelongsToMany   D

Complexity

Total Complexity 77

Size/Duplication

Total Lines 897
Duplicated Lines 3.68 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
dl 33
loc 897
rs 4.2105
c 0
b 0
f 0
wmc 77
lcom 1
cbo 12

51 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A detachMany() 0 6 1
A getIdsFromHashes() 0 10 2
A getResults() 0 8 1
A wherePivot() 0 4 1
A orWherePivot() 0 4 1
A getPivotAttributes() 0 4 1
A first() 0 6 2
A firstOrFail() 0 8 2
A get() 0 15 2
A hydratePivotRelation() 0 14 1
A cleanPivotAttributes() 0 22 3
A addConstraints() 0 8 2
A getRelationCountQuery() 0 10 2
A getRelationCountQueryForSelfJoin() 0 12 1
A getRelationCountHash() 0 4 1
A getSelectColumns() 0 8 2
A getAliasedPivotColumns() 0 15 2
A setJoin() 0 15 2
A setWhere() 0 10 1
A addEagerConstraints() 0 4 1
B match() 33 33 2
A buildDictionary() 0 16 2
A getRelatedIds() 0 6 1
A updatePivot() 0 9 1
A updatePivots() 0 6 2
A createPivots() 0 15 2
A updateExistingPivot() 0 8 2
A attach() 0 6 1
A sync() 0 4 1
A detachExcept() 0 17 2
A createAttachRecords() 0 15 2
A attacher() 0 11 1
A getAttachId() 0 8 2
A createAttachRecord() 0 19 2
A setTimestampsOnAttach() 0 12 2
A getModelKeysFromCollection() 0 8 1
A detach() 0 22 3
A newPivotQuery() 0 8 1
A newPivotStatement() 0 4 1
A newPivotStatementForId() 0 10 1
A newPivot() 0 6 1
A newExistingPivot() 0 4 1
A withPivot() 0 8 2
A withTimestamps() 0 4 3
A getHasCompareKey() 0 4 1
A getForeignKey() 0 4 1
A getOtherKey() 0 4 1
A getQualifiedParentKeyName() 0 4 1
A getTable() 0 4 1
A getRelationName() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like BelongsToMany often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BelongsToMany, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Analogue\ORM\Relationships;
4
5
use Analogue\ORM\Mappable;
6
use Analogue\ORM\System\Query;
7
use Analogue\ORM\System\Mapper;
8
use Analogue\ORM\EntityCollection;
9
use Illuminate\Database\Query\Expression;
10
use Analogue\ORM\System\InternallyMappable;
11
use Analogue\ORM\Exceptions\EntityNotFoundException;
12
13
class BelongsToMany extends Relationship
14
{
15
    /**
16
     * The intermediate table for the relation.
17
     *
18
     * @var string
19
     */
20
    protected $table;
21
22
    /**
23
     * The foreign key of the parent model.
24
     *
25
     * @var string
26
     */
27
    protected $foreignKey;
28
29
    /**
30
     * The associated key of the relation.
31
     *
32
     * @var string
33
     */
34
    protected $otherKey;
35
36
    /**
37
     * The "name" of the relationship.
38
     *
39
     * @var string
40
     */
41
    protected $relationName;
42
43
    /**
44
     * The pivot table columns to retrieve.
45
     *
46
     * @var array
47
     */
48
    protected $pivotColumns = [];
49
50
    /**
51
     * This relationship has pivot attributes
52
     *
53
     * @var boolean
54
     */
55
    protected static $hasPivot = true;
56
57
    /**
58
     * Create a new has many relationship instance.
59
     *
60
     * @param Mapper   $mapper
61
     * @param Mappable $parent
62
     * @param string   $table
63
     * @param string   $foreignKey
64
     * @param string   $otherKey
65
     * @param string   $relationName
66
     */
67
    public function __construct(Mapper $mapper, $parent, $table, $foreignKey, $otherKey, $relationName = null)
68
    {
69
        $this->table = $table;
70
        $this->otherKey = $otherKey;
71
        $this->foreignKey = $foreignKey;
72
        $this->relationName = $relationName;
73
74
        parent::__construct($mapper, $parent);
75
    }
76
77
    /**
78
     * @param $related
79
     */
80
    public function detachMany($related)
81
    {
82
        $ids = $this->getIdsFromHashes($related);
83
84
        $this->detach($ids);
85
    }
86
87
    /**
88
     * @param array $hashes
89
     * @return array
90
     */
91
    protected function getIdsFromHashes(array $hashes)
92
    {
93
        $ids = [];
94
95
        foreach ($hashes as $hash) {
96
            $split = explode('.', $hash);
97
            $ids[] = $split[1];
98
        }
99
        return $ids;
100
    }
101
102
    /**
103
     * Get the results of the relationship.
104
     *
105
     * @param $relation
106
     *
107
     * @return EntityCollection
108
     */
109
    public function getResults($relation)
110
    {
111
        $results = $this->get();
112
113
        $this->cacheRelation($results, $relation);
114
115
        return $results;
116
    }
117
118
    /**
119
     * Set a where clause for a pivot table column.
120
     *
121
     * @param  string $column
122
     * @param  string $operator
123
     * @param  mixed  $value
124
     * @param  string $boolean
125
     * @return self
126
     */
127
    public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
128
    {
129
        return $this->where($this->table . '.' . $column, $operator, $value, $boolean);
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Analogue\ORM\Relationships\BelongsToMany>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
130
    }
131
132
    /**
133
     * Set an or where clause for a pivot table column.
134
     *
135
     * @param  string $column
136
     * @param  string $operator
137
     * @param  mixed  $value
138
     * @return self
139
     */
140
    public function orWherePivot($column, $operator = null, $value = null)
141
    {
142
        return $this->wherePivot($column, $operator, $value, 'or');
143
    }
144
145
    /**
146
     * Return Pivot attributes when available on a relationship
147
     *
148
     * @return array
149
     */
150
    public function getPivotAttributes()
151
    {
152
        return $this->pivotColumns;
153
    }
154
155
    /**
156
     * Execute the query and get the first result.
157
     *
158
     * @param  array $columns
159
     * @return mixed
160
     */
161
    public function first($columns = ['*'])
162
    {
163
        $results = $this->take(1)->get($columns);
0 ignored issues
show
Documentation Bug introduced by
The method take does not exist on object<Analogue\ORM\Relationships\BelongsToMany>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
164
165
        return count($results) > 0 ? $results->first() : null;
166
    }
167
168
    /**
169
     * Execute the query and get the first result or throw an exception.
170
     *
171
     * @param  array $columns
172
     *
173
     * @throws EntityNotFoundException
174
     *
175
     * @return Mappable|self
176
     */
177
    public function firstOrFail($columns = ['*'])
178
    {
179
        if (!is_null($entity = $this->first($columns))) {
180
            return $entity;
181
        }
182
183
        throw new EntityNotFoundException;
184
    }
185
186
    /**
187
     * Execute the query as a "select" statement.
188
     *
189
     * @param  array $columns
190
     * @return \Analogue\ORM\EntityCollection
191
     */
192
    public function get($columns = ['*'])
193
    {
194
        // First we'll add the proper select columns onto the query so it is run with
195
        // the proper columns. Then, we will get the results and hydrate out pivot
196
        // models with the result of those columns as a separate model relation.
197
        $columns = $this->query->getQuery()->columns ? [] : $columns;
198
199
        $select = $this->getSelectColumns($columns);
200
201
        $entities = $this->query->addSelect($select)->getEntities();
0 ignored issues
show
Documentation Bug introduced by
The method addSelect does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
202
203
        $entities = $this->hydratePivotRelation($entities);
204
        
205
        return $this->relatedMap->newCollection($entities);
206
    }
207
208
    /**
209
     * Hydrate the pivot table relationship on the models.
210
     *
211
     * @param  array $entities
212
     * @return void
213
     */
214
    protected function hydratePivotRelation(array $entities)
215
    {
216
        // TODO (note) We should definitely get rid of the pivot in a next
217
        // release, as this is not quite relevant in a datamapper context. 
218
        $host = $this;
219
        return array_map(function($entity) use ($host) {
220
            $entityWrapper = $this->factory->make($entity);
221
222
            $pivot = $this->newExistingPivot($this->cleanPivotAttributes($entityWrapper));
223
            $entityWrapper->setEntityAttribute('pivot', $pivot);
0 ignored issues
show
Documentation introduced by
$pivot is of type object<Analogue\ORM\Relationships\Pivot>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
224
225
            return $entityWrapper->getObject();
226
        }, $entities);
227
    }
228
229
    /**
230
     * Get the pivot attributes from a model.
231
     *
232
     * @param  $entity
233
     * @return array
234
     */
235
    protected function cleanPivotAttributes(InternallyMappable $entity)
236
    {
237
        $values = [];
238
239
        $attributes = $entity->getEntityAttributes();
240
241
        foreach ($attributes as $key => $value) {
242
            // To get the pivots attributes we will just take any of the attributes which
243
            // begin with "pivot_" and add those to this arrays, as well as unsetting
244
            // them from the parent's models since they exist in a different table.
245
            if (strpos($key, 'pivot_') === 0) {
246
                $values[substr($key, 6)] = $value;
247
248
                unset($attributes[$key]);
249
            }
250
        }
251
252
        // Rehydrate Entity with cleaned array.
253
        $entity->setEntityAttributes($attributes);
254
255
        return $values;
256
    }
257
258
    /**
259
     * Set the base constraints on the relation query.
260
     *
261
     * @return void
262
     */
263
    public function addConstraints()
264
    {
265
        $this->setJoin();
266
267
        if (static::$constraints) {
268
            $this->setWhere();
269
        }
270
    }
271
272
    /**
273
     * Add the constraints for a relationship count query.
274
     *
275
     * @param  Query $query
276
     * @param  Query $parent
277
     * @return Query
278
     */
279
    public function getRelationCountQuery(Query $query, Query $parent)
280
    {
281
        if ($parent->getQuery()->from == $query->getQuery()->from) {
282
            return $this->getRelationCountQueryForSelfJoin($query, $parent);
283
        }
284
285
        $this->setJoin($query);
286
287
        return parent::getRelationCountQuery($query, $parent);
288
    }
289
290
    /**
291
     * Add the constraints for a relationship count query on the same table.
292
     *
293
     * @param  Query $query
294
     * @param  Query $parent
295
     * @return Query
296
     */
297
    public function getRelationCountQueryForSelfJoin(Query $query, Query $parent)
0 ignored issues
show
Unused Code introduced by
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
298
    {
299
        $query->select(new Expression('count(*)'));
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
300
301
        $tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();
302
303
        $query->from($this->table . ' as ' . $tablePrefix . $hash = $this->getRelationCountHash());
0 ignored issues
show
Documentation Bug introduced by
The method from does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
304
305
        $key = $this->wrap($this->getQualifiedParentKeyName());
306
307
        return $query->where($hash . '.' . $this->foreignKey, '=', new Expression($key));
308
    }
309
310
    /**
311
     * Get a relationship join table hash.
312
     *
313
     * @return string
314
     */
315
    public function getRelationCountHash()
316
    {
317
        return 'self_' . md5(microtime(true));
318
    }
319
320
    /**
321
     * Set the select clause for the relation query.
322
     *
323
     * @param  array $columns
324
     * @return \Analogue\ORM\Relationships\BelongsToMany
325
     */
326
    protected function getSelectColumns(array $columns = ['*'])
327
    {
328
        if ($columns == ['*']) {
329
            $columns = [$this->relatedMap->getTable() . '.*'];
330
        }
331
332
        return array_merge($columns, $this->getAliasedPivotColumns());
333
    }
334
335
    /**
336
     * Get the pivot columns for the relation.
337
     *
338
     * @return array
339
     */
340
    protected function getAliasedPivotColumns()
341
    {
342
        $defaults = [$this->foreignKey, $this->otherKey];
343
344
        // We need to alias all of the pivot columns with the "pivot_" prefix so we
345
        // can easily extract them out of the models and put them into the pivot
346
        // relationships when they are retrieved and hydrated into the models.
347
        $columns = [];
348
349
        foreach (array_merge($defaults, $this->pivotColumns) as $column) {
350
            $columns[] = $this->table . '.' . $column . ' as pivot_' . $column;
351
        }
352
353
        return array_unique($columns);
354
    }
355
356
    /**
357
     * Set the join clause for the relation query.
358
     *
359
     * @param  \Analogue\ORM\Query|null
360
     * @return $this
361
     */
362
    protected function setJoin($query = null)
363
    {
364
        $query = $query ?: $this->query;
365
366
        // We need to join to the intermediate table on the related model's primary
367
        // key column with the intermediate table's foreign key for the related
368
        // model instance. Then we can set the "where" for the parent models.
369
        $baseTable = $this->relatedMap->getTable();
370
371
        $key = $baseTable . '.' . $this->relatedMap->getKeyName();
372
373
        $query->join($this->table, $key, '=', $this->getOtherKey());
374
375
        return $this;
376
    }
377
378
    /**
379
     * Set the where clause for the relation query.
380
     *
381
     * @return $this
382
     */
383
    protected function setWhere()
384
    {
385
        $foreign = $this->getForeignKey();
386
387
        $parentKey = $this->parentMap->getKeyName();
388
389
        $this->query->where($foreign, '=', $this->parent->getEntityAttribute($parentKey));
390
391
        return $this;
392
    }
393
394
    /**
395
     * Set the constraints for an eager load of the relation.
396
     *
397
     * @param  array $results
398
     * @return void
399
     */
400
    public function addEagerConstraints(array $results)
401
    {
402
        $this->query->whereIn($this->getForeignKey(), $this->getKeysFromResults($results));
0 ignored issues
show
Documentation Bug introduced by
The method whereIn does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
403
    }
404
405
    /**
406
     * Match Eagerly loaded relation to result
407
     *
408
     * @param  array            $results
409
     * @param  string           $relation
410
     * @return array
411
     */
412 View Code Duplication
    public function match(array $results, $relation)
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...
413
    {
414
        $entities = $this->getEager();
415
416
        // TODO; optimize this operation
417
        $dictionary = $this->buildDictionary($entities);
418
419
        $keyName = $this->relatedMap->getKeyName();
420
421
        $cache = $this->parentMapper->getEntityCache();
422
423
        $host = $this;
424
425
        // Once we have an array dictionary of child objects we can easily match the
426
        // children back to their parent using the dictionary and the keys on the
427
        // the parent models. Then we will return the hydrated models back out.
428
        return array_map(function($result) use ($dictionary, $keyName, $cache, $relation, $host) {
429
430
            if (isset($dictionary[$key = $result[$keyName]])) {
431
                $collection = $host->relatedMap->newCollection($dictionary[$key]);
432
433
                $result[$relation] = $collection;
434
435
                // TODO Refactor this
436
                $cache->cacheLoadedRelationResult($key, $relation, $collection, $this);
437
            }
438
            else {
439
                $result[$relation] = $host->relatedMap->newCollection();
440
            }
441
            return $result;
442
443
        }, $results);
444
    }
445
446
    /**
447
     * Build model dictionary keyed by the relation's foreign key.
448
     *
449
     * @param  EntityCollection $results
450
     * @return array
451
     */
452
    protected function buildDictionary(EntityCollection $results)
453
    {
454
        $foreign = $this->foreignKey;
455
456
        // First we will build a dictionary of child models keyed by the foreign key
457
        // of the relation so that we will easily and quickly match them to their
458
        // parents without having a possibly slow inner loops for every models.
459
        $dictionary = [];
460
461
        foreach ($results as $entity) {
462
            $wrapper = $this->factory->make($entity);
463
            $dictionary[$wrapper->getEntityAttribute('pivot')->$foreign][] = $entity;
464
        }
465
466
        return $dictionary;
467
    }
468
469
    /**
470
     * Get all of the IDs for the related models.
471
     *
472
     * @return array
473
     */
474
    public function getRelatedIds()
475
    {
476
        $fullKey = $this->relatedMap->getQualifiedKeyName();
477
478
        return $this->getQuery()->select($fullKey)->lists($this->relatedMap->getKeyName());
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<Analogue\ORM\System\Query>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
479
    }
480
481
    /**
482
     * Update Pivot
483
     *
484
     * @param  \Analogue\ORM\Entity $entity
485
     * @return void
486
     */
487
    public function updatePivot($entity)
488
    {
489
        $keyName = $this->relatedMap->getKeyName();
490
491
        $this->updateExistingPivot(
492
            $entity->getEntityAttribute($keyName),
493
            $entity->getEntityAttribute('pivot')->getEntityAttributes()
494
        );
495
    }
496
497
    /**
498
     * Update Multiple pivot
499
     *
500
     * @param  $relatedEntities
501
     * @return void
502
     */
503
    public function updatePivots($relatedEntities)
504
    {
505
        foreach ($relatedEntities as $entity) {
506
            $this->updatePivot($entity);
507
        }
508
    }
509
510
    /**
511
     * Create Pivot Records
512
     *
513
     * @param \Analogue\ORM\Entity[] $relatedEntities
514
     * @return void
515
     */
516
    public function createPivots($relatedEntities)
517
    {
518
        $keys = [];
519
        $attributes = [];
520
521
        $keyName = $this->relatedMap->getKeyName();
522
523
        foreach ($relatedEntities as $entity) {
524
            $keys[] = $entity->getEntityAttribute($keyName);
525
        }
526
527
        $records = $this->createAttachRecords($keys, $attributes);
528
529
        $this->query->getQuery()->from($this->table)->insert($records);
530
    }
531
532
    /**
533
     * Update an existing pivot record on the table.
534
     *
535
     * @param  mixed $id
536
     * @param  array $attributes
537
     * @throws \InvalidArgumentException
538
     * @return integer
539
     */
540
    public function updateExistingPivot($id, array $attributes)
541
    {
542
        if (in_array($this->updatedAt(), $this->pivotColumns)) {
543
            $attributes = $this->setTimestampsOnAttach($attributes, true);
544
        }
545
546
        return $this->newPivotStatementForId($id)->update($attributes);
547
    }
548
549
    /**
550
     * Attach a model to the parent.
551
     *
552
     * @param  mixed $id
553
     * @param  array $attributes
554
     * @return void
555
     */
556
    public function attach($id, array $attributes = [])
557
    {
558
        $query = $this->newPivotStatement();
559
560
        $query->insert($this->createAttachRecords((array) $id, $attributes));
561
    }
562
563
    /**
564
     * @param  array $entities
565
     *
566
     * @throws \InvalidArgumentException
567
     */
568
    public function sync(array $entities)
569
    {
570
        $this->detachExcept($entities);
571
    }
572
573
    /**
574
     * Detach related entities that are not in $id
575
     *
576
     * @param  array $entities
577
     *
578
     * @throws \InvalidArgumentException
579
     *
580
     * @return void
581
     */
582
    protected function detachExcept(array $entities = [])
583
    {
584
        $query = $this->newPivotQuery();
585
586
        if (count($entities) > 0) {
587
            $keys = $this->getKeys($entities);
588
589
            $query->whereNotIn($this->otherKey, $keys);
590
        }
591
        $parentKey = $this->parentMap->getKeyName();
592
593
        $query->where($this->foreignKey, '=', $this->parent->getEntityAttribute($parentKey));
594
        
595
        $query->delete();
596
597
        $query = $this->newPivotQuery();
0 ignored issues
show
Unused Code introduced by
$query is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
598
    }
599
600
601
    /**
602
     * Create an array of records to insert into the pivot table.
603
     *
604
     * @param  array $ids
605
     * @param  array $attributes
606
     * @return array
607
     */
608
    protected function createAttachRecords($ids, array $attributes)
609
    {
610
        $records = [];
611
612
        $timed = in_array($this->createdAt(), $this->pivotColumns);
613
614
        // To create the attachment records, we will simply spin through the IDs given
615
        // and create a new record to insert for each ID. Each ID may actually be a
616
        // key in the array, with extra attributes to be placed in other columns.
617
        foreach ($ids as $key => $value) {
618
            $records[] = $this->attacher($key, $value, $attributes, $timed);
619
        }
620
        
621
        return $records;
622
    }
623
624
    /**
625
     * Create a full attachment record payload.
626
     *
627
     * @param  int   $key
628
     * @param  mixed $value
629
     * @param  array $attributes
630
     * @param  bool  $timed
631
     * @return array
632
     */
633
    protected function attacher($key, $value, $attributes, $timed)
634
    {
635
        list($id, $extra) = $this->getAttachId($key, $value, $attributes);
636
        
637
        // To create the attachment records, we will simply spin through the IDs given
638
        // and create a new record to insert for each ID. Each ID may actually be a
639
        // key in the array, with extra attributes to be placed in other columns.
640
        $record = $this->createAttachRecord($id, $timed);
641
642
        return array_merge($record, $extra);
643
    }
644
645
    /**
646
     * Get the attach record ID and extra attributes.
647
     *
648
     * @param  int   $key
649
     * @param  mixed $value
650
     * @param  array $attributes
651
     * @return array
652
     */
653
    protected function getAttachId($key, $value, array $attributes)
654
    {
655
        if (is_array($value)) {
656
            return [$key, array_merge($value, $attributes)];
657
        }
658
659
        return [$value, $attributes];
660
    }
661
662
    /**
663
     * Create a new pivot attachment record.
664
     *
665
     * @param  int  $id
666
     * @param  bool $timed
667
     * @return array
668
     */
669
    protected function createAttachRecord($id, $timed)
670
    {
671
        $parentKey = $this->parentMap->getKeyName();
672
673
        $record = [];
674
675
        $record[$this->foreignKey] = $this->parent->getEntityAttribute($parentKey);
676
677
        $record[$this->otherKey] = $id;
678
679
        // If the record needs to have creation and update timestamps, we will make
680
        // them by calling the parent model's "freshTimestamp" method which will
681
        // provide us with a fresh timestamp in this model's preferred format.
682
        if ($timed) {
683
            $record = $this->setTimestampsOnAttach($record);
684
        }
685
686
        return $record;
687
    }
688
689
    /**
690
     * Set the creation and update timestamps on an attach record.
691
     *
692
     * @param  array $record
693
     * @param  bool  $exists
694
     * @return array
695
     */
696
    protected function setTimestampsOnAttach(array $record, $exists = false)
697
    {
698
        $fresh = $this->freshTimestamp();
699
700
        if (!$exists) {
701
            $record[$this->createdAt()] = $fresh;
702
        }
703
704
        $record[$this->updatedAt()] = $fresh;
705
706
        return $record;
707
    }
708
709
    /**
710
     * @param EntityCollection $entities
711
     * @return array
712
     */
713
    protected function getModelKeysFromCollection(EntityCollection $entities)
714
    {
715
        $keyName = $this->relatedMap->getKeyName();
716
717
        return array_map(function ($m) use ($keyName) {
718
            return $m->$keyName;
719
        }, $entities);
720
    }
721
722
    /**
723
     * Detach models from the relationship.
724
     *
725
     * @param  int|array $ids
726
     * @throws \InvalidArgumentException
727
     * @return int
728
     */
729
    public function detach($ids = [])
730
    {
731
        if ($ids instanceof EntityCollection) {
732
            $ids = (array) $ids->modelKeys();
733
        }
734
735
        $query = $this->newPivotQuery();
736
737
        // If associated IDs were passed to the method we will only delete those
738
        // associations, otherwise all of the association ties will be broken.
739
        // We'll return the numbers of affected rows when we do the deletes.
740
        $ids = (array) $ids;
741
742
        if (count($ids) > 0) {
743
            $query->whereIn($this->otherKey, (array) $ids);
744
        }
745
746
        // Once we have all of the conditions set on the statement, we are ready
747
        // to run the delete on the pivot table. Then, if the touch parameter
748
        // is true, we will go ahead and touch all related models to sync.
749
        return $query->delete();
750
    }
751
    
752
    /**
753
     * Create a new query builder for the pivot table.
754
     *
755
     * @throws \InvalidArgumentException
756
     *
757
     * @return \Illuminate\Database\Query\Builder
758
     */
759
    protected function newPivotQuery()
760
    {
761
        $query = $this->newPivotStatement();
762
763
        $parentKey = $this->parentMap->getKeyName();
764
765
        return $query->where($this->foreignKey, $this->parent->getEntityAttribute($parentKey));
766
    }
767
768
    /**
769
     * Get a new plain query builder for the pivot table.
770
     *
771
     * @return \Illuminate\Database\Query\Builder
772
     */
773
    public function newPivotStatement()
774
    {
775
        return $this->query->getQuery()->newQuery()->from($this->table);
776
    }
777
778
    /**
779
     * Get a new pivot statement for a given "other" ID.
780
     *
781
     * @param  mixed $id
782
     *
783
     * @throws \InvalidArgumentException
784
     *
785
     * @return \Illuminate\Database\Query\Builder
786
     */
787
    public function newPivotStatementForId($id)
788
    {
789
        $pivot = $this->newPivotStatement();
790
791
        $parentKeyName = $this->parentMap->getKeyName();
792
793
        $key = $this->parent->getEntityAttribute($parentKeyName);
794
795
        return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
796
    }
797
798
    /**
799
     * Create a new pivot model instance.
800
     *
801
     * @param  array $attributes
802
     * @param  bool  $exists
803
     * @return \Analogue\ORM\Relationships\Pivot
804
     */
805
    public function newPivot(array $attributes = [], $exists = false)
806
    {
807
        $pivot = new Pivot($this->parent, $this->parentMap, $attributes, $this->table, $exists);
808
        
809
        return $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
810
    }
811
812
    /**
813
     * Create a new existing pivot model instance.
814
     *
815
     * @param  array $attributes
816
     * @return \Analogue\ORM\Relationships\Pivot
817
     */
818
    public function newExistingPivot(array $attributes = [])
819
    {
820
        return $this->newPivot($attributes, true);
821
    }
822
823
    /**
824
     * Set the columns on the pivot table to retrieve.
825
     *
826
     * @param  array $columns
827
     * @return $this
828
     */
829
    public function withPivot($columns)
830
    {
831
        $columns = is_array($columns) ? $columns : func_get_args();
832
833
        $this->pivotColumns = array_merge($this->pivotColumns, $columns);
834
835
        return $this;
836
    }
837
838
    /**
839
     * Specify that the pivot table has creation and update timestamps.
840
     *
841
     * @param  mixed $createdAt
842
     * @param  mixed $updatedAt
843
     * @return \Analogue\ORM\Relationships\BelongsToMany
844
     */
845
    public function withTimestamps($createdAt = null, $updatedAt = null)
846
    {
847
        return $this->withPivot($createdAt ?: $this->createdAt(), $updatedAt ?: $this->updatedAt());
848
    }
849
850
    /**
851
     * Get the key for comparing against the parent key in "has" query.
852
     *
853
     * @return string
854
     */
855
    public function getHasCompareKey()
856
    {
857
        return $this->getForeignKey();
858
    }
859
860
    /**
861
     * Get the fully qualified foreign key for the relation.
862
     *
863
     * @return string
864
     */
865
    public function getForeignKey()
866
    {
867
        return $this->table . '.' . $this->foreignKey;
868
    }
869
870
    /**
871
     * Get the fully qualified "other key" for the relation.
872
     *
873
     * @return string
874
     */
875
    public function getOtherKey()
876
    {
877
        return $this->table . '.' . $this->otherKey;
878
    }
879
880
    /**
881
     * Get the fully qualified parent key name.
882
     *
883
     * @return string
884
     */
885
    protected function getQualifiedParentKeyName()
886
    {
887
        return $this->parentMap->getQualifiedKeyName();
888
    }
889
890
    /**
891
     * Get the intermediate table for the relationship.
892
     *
893
     * @return string
894
     */
895
    public function getTable()
896
    {
897
        return $this->table;
898
    }
899
900
    /**
901
     * Get the relationship name for the relationship.
902
     *
903
     * @return string
904
     */
905
    public function getRelationName()
906
    {
907
        return $this->relationName;
908
    }
909
}
910