BelongsToMany   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 935
Duplicated Lines 2.67 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 16
Bugs 9 Features 1
Metric Value
wmc 84
c 16
b 9
f 1
lcom 1
cbo 12
dl 25
loc 935
rs 3.4285

54 Methods

Rating   Name   Duplication   Size   Complexity  
A getPivotAttributes() 0 4 1
A sync() 0 4 1
A getModelKeysFromCollection() 0 8 1
A detachExcept() 0 17 2
A __construct() 0 9 1
A attachTo() 0 3 1
A detachFrom() 0 6 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 first() 0 6 2
A firstOrFail() 0 8 2
A get() 0 22 3
A hydratePivotRelation() 0 14 2
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
A initRelation() 0 10 2
B match() 25 25 3
A buildDictionary() 0 17 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 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 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
     * @return mixed
80
     */
81
    public function attachTo($related)
82
    {
83
    }
84
85
    /**
86
     * @param  $related
87
     * @return mixed
88
     */
89
    public function detachFrom($related)
90
    {
91
        $ids = $this->getIdsFromHashes([$related]);
92
93
        $this->detach($ids);
94
    }
95
96
    /**
97
     * @param $related
98
     */
99
    public function detachMany($related)
100
    {
101
        $ids = $this->getIdsFromHashes($related);
102
103
        $this->detach($ids);
104
    }
105
106
    /**
107
     * @param array $hashes
108
     * @return array
109
     */
110
    protected function getIdsFromHashes(array $hashes)
111
    {
112
        $ids = [];
113
114
        foreach ($hashes as $hash) {
115
            $split = explode('.', $hash);
116
            $ids[] = $split[1];
117
        }
118
        return $ids;
119
    }
120
121
    /**
122
     * Get the results of the relationship.
123
     *
124
     * @param $relation
125
     *
126
     * @return EntityCollection
127
     */
128
    public function getResults($relation)
129
    {
130
        $results = $this->get();
131
132
        $this->cacheRelation($results, $relation);
133
134
        return $results;
135
    }
136
137
    /**
138
     * Set a where clause for a pivot table column.
139
     *
140
     * @param  string $column
141
     * @param  string $operator
142
     * @param  mixed  $value
143
     * @param  string $boolean
144
     * @return self
145
     */
146
    public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
147
    {
148
        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...
149
    }
150
151
    /**
152
     * Set an or where clause for a pivot table column.
153
     *
154
     * @param  string $column
155
     * @param  string $operator
156
     * @param  mixed  $value
157
     * @return self
158
     */
159
    public function orWherePivot($column, $operator = null, $value = null)
160
    {
161
        return $this->wherePivot($column, $operator, $value, 'or');
162
    }
163
164
    /**
165
     * Return Pivot attributes when available on a relationship
166
     *
167
     * @return array
168
     */
169
    public function getPivotAttributes()
170
    {
171
        return $this->pivotColumns;
172
    }
173
174
    /**
175
     * Execute the query and get the first result.
176
     *
177
     * @param  array $columns
178
     * @return mixed
179
     */
180
    public function first($columns = ['*'])
181
    {
182
        $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...
183
184
        return count($results) > 0 ? $results->first() : null;
185
    }
186
187
    /**
188
     * Execute the query and get the first result or throw an exception.
189
     *
190
     * @param  array $columns
191
     *
192
     * @throws EntityNotFoundException
193
     *
194
     * @return Mappable|self
195
     */
196
    public function firstOrFail($columns = ['*'])
197
    {
198
        if (!is_null($entity = $this->first($columns))) {
199
            return $entity;
200
        }
201
202
        throw new EntityNotFoundException;
203
    }
204
205
    /**
206
     * Execute the query as a "select" statement.
207
     *
208
     * @param  array $columns
209
     * @return \Analogue\ORM\EntityCollection
210
     */
211
    public function get($columns = ['*'])
212
    {
213
        // First we'll add the proper select columns onto the query so it is run with
214
        // the proper columns. Then, we will get the results and hydrate out pivot
215
        // models with the result of those columns as a separate model relation.
216
        $columns = $this->query->getQuery()->columns ? [] : $columns;
217
218
        $select = $this->getSelectColumns($columns);
219
220
        $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...
221
222
        $this->hydratePivotRelation($entities);
223
224
        // If we actually found models we will also eager load any relationships that
225
        // have been specified as needing to be eager loaded. This will solve the
226
        // n + 1 query problem for the developer and also increase performance.
227
        if (count($entities) > 0) {
228
            $entities = $this->query->eagerLoadRelations($entities);
229
        }
230
231
        return $this->relatedMap->newCollection($entities);
232
    }
233
234
    /**
235
     * Hydrate the pivot table relationship on the models.
236
     *
237
     * @param  array $entities
238
     * @return void
239
     */
240
    protected function hydratePivotRelation(array $entities)
241
    {
242
        // To hydrate the pivot relationship, we will just gather the pivot attributes
243
        // and create a new Pivot model, which is basically a dynamic model that we
244
        // will set the attributes, table, and connections on so it they be used.
245
246
        foreach ($entities as $entity) {
247
            $entityWrapper = $this->factory->make($entity);
248
249
            $pivot = $this->newExistingPivot($this->cleanPivotAttributes($entityWrapper));
250
251
            $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...
252
        }
253
    }
254
255
    /**
256
     * Get the pivot attributes from a model.
257
     *
258
     * @param  $entity
259
     * @return array
260
     */
261
    protected function cleanPivotAttributes(InternallyMappable $entity)
262
    {
263
        $values = [];
264
265
        $attributes = $entity->getEntityAttributes();
266
267
        foreach ($attributes as $key => $value) {
268
            // To get the pivots attributes we will just take any of the attributes which
269
            // begin with "pivot_" and add those to this arrays, as well as unsetting
270
            // them from the parent's models since they exist in a different table.
271
            if (strpos($key, 'pivot_') === 0) {
272
                $values[substr($key, 6)] = $value;
273
274
                unset($attributes[$key]);
275
            }
276
        }
277
278
        // Rehydrate Entity with cleaned array.
279
        $entity->setEntityAttributes($attributes);
280
281
        return $values;
282
    }
283
284
    /**
285
     * Set the base constraints on the relation query.
286
     *
287
     * @return void
288
     */
289
    public function addConstraints()
290
    {
291
        $this->setJoin();
292
293
        if (static::$constraints) {
294
            $this->setWhere();
295
        }
296
    }
297
298
    /**
299
     * Add the constraints for a relationship count query.
300
     *
301
     * @param  Query $query
302
     * @param  Query $parent
303
     * @return Query
304
     */
305
    public function getRelationCountQuery(Query $query, Query $parent)
306
    {
307
        if ($parent->getQuery()->from == $query->getQuery()->from) {
308
            return $this->getRelationCountQueryForSelfJoin($query, $parent);
309
        }
310
311
        $this->setJoin($query);
312
313
        return parent::getRelationCountQuery($query, $parent);
314
    }
315
316
    /**
317
     * Add the constraints for a relationship count query on the same table.
318
     *
319
     * @param  Query $query
320
     * @param  Query $parent
321
     * @return Query
322
     */
323
    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...
324
    {
325
        $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...
326
327
        $tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();
328
329
        $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...
330
331
        $key = $this->wrap($this->getQualifiedParentKeyName());
332
333
        return $query->where($hash . '.' . $this->foreignKey, '=', new Expression($key));
334
    }
335
336
    /**
337
     * Get a relationship join table hash.
338
     *
339
     * @return string
340
     */
341
    public function getRelationCountHash()
342
    {
343
        return 'self_' . md5(microtime(true));
344
    }
345
346
    /**
347
     * Set the select clause for the relation query.
348
     *
349
     * @param  array $columns
350
     * @return \Analogue\ORM\Relationships\BelongsToMany
351
     */
352
    protected function getSelectColumns(array $columns = ['*'])
353
    {
354
        if ($columns == ['*']) {
355
            $columns = [$this->relatedMap->getTable() . '.*'];
356
        }
357
358
        return array_merge($columns, $this->getAliasedPivotColumns());
359
    }
360
361
    /**
362
     * Get the pivot columns for the relation.
363
     *
364
     * @return array
365
     */
366
    protected function getAliasedPivotColumns()
367
    {
368
        $defaults = [$this->foreignKey, $this->otherKey];
369
370
        // We need to alias all of the pivot columns with the "pivot_" prefix so we
371
        // can easily extract them out of the models and put them into the pivot
372
        // relationships when they are retrieved and hydrated into the models.
373
        $columns = [];
374
375
        foreach (array_merge($defaults, $this->pivotColumns) as $column) {
376
            $columns[] = $this->table . '.' . $column . ' as pivot_' . $column;
377
        }
378
379
        return array_unique($columns);
380
    }
381
382
    /**
383
     * Set the join clause for the relation query.
384
     *
385
     * @param  \Analogue\ORM\Query|null
386
     * @return $this
387
     */
388
    protected function setJoin($query = null)
389
    {
390
        $query = $query ?: $this->query;
391
392
        // We need to join to the intermediate table on the related model's primary
393
        // key column with the intermediate table's foreign key for the related
394
        // model instance. Then we can set the "where" for the parent models.
395
        $baseTable = $this->relatedMap->getTable();
396
397
        $key = $baseTable . '.' . $this->relatedMap->getKeyName();
398
399
        $query->join($this->table, $key, '=', $this->getOtherKey());
400
401
        return $this;
402
    }
403
404
    /**
405
     * Set the where clause for the relation query.
406
     *
407
     * @return $this
408
     */
409
    protected function setWhere()
410
    {
411
        $foreign = $this->getForeignKey();
412
413
        $parentKey = $this->parentMap->getKeyName();
414
415
        $this->query->where($foreign, '=', $this->parent->getEntityAttribute($parentKey));
416
417
        return $this;
418
    }
419
420
    /**
421
     * Set the constraints for an eager load of the relation.
422
     *
423
     * @param  array $entities
424
     * @return void
425
     */
426
    public function addEagerConstraints(array $entities)
427
    {
428
        $this->query->whereIn($this->getForeignKey(), $this->getKeys($entities));
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...
429
    }
430
431
    /**
432
     * Initialize the relation on a set of eneities.
433
     *
434
     * @param  array  $entities
435
     * @param  string $relation
436
     * @return array
437
     */
438
    public function initRelation(array $entities, $relation)
439
    {
440
        foreach ($entities as $entity) {
441
            $entity = $this->factory->make($entity);
442
443
            $entity->setEntityAttribute($relation, $this->relatedMap->newCollection());
444
        }
445
446
        return $entities;
447
    }
448
449
    /**
450
     * Match the eagerly loaded results to their parents.
451
     *
452
     * @param  array            $entities
453
     * @param  EntityCollection $results
454
     * @param  string           $relation
455
     * @return array
456
     */
457 View Code Duplication
    public function match(array $entities, EntityCollection $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...
458
    {
459
        $dictionary = $this->buildDictionary($results);
460
461
        $keyName = $this->relatedMap->getKeyName();
462
463
        $cache = $this->parentMapper->getEntityCache();
464
465
        // Once we have an array dictionary of child objects we can easily match the
466
        // children back to their parent using the dictionary and the keys on the
467
        // the parent models. Then we will return the hydrated models back out.
468
        foreach ($entities as $entity) {
469
            $wrapper = $this->factory->make($entity);
470
471
            if (isset($dictionary[$key = $wrapper->getEntityAttribute($keyName)])) {
472
                $collection = $this->relatedMap->newCollection($dictionary[$key]);
473
474
                $wrapper->setEntityAttribute($relation, $collection);
475
476
                $cache->cacheLoadedRelationResult($entity, $relation, $collection, $this);
477
            }
478
        }
479
480
        return $entities;
481
    }
482
483
    /**
484
     * Build model dictionary keyed by the relation's foreign key.
485
     *
486
     * @param  EntityCollection $results
487
     * @return array
488
     */
489
    protected function buildDictionary(EntityCollection $results)
490
    {
491
        $foreign = $this->foreignKey;
492
493
        // First we will build a dictionary of child models keyed by the foreign key
494
        // of the relation so that we will easily and quickly match them to their
495
        // parents without having a possibly slow inner loops for every models.
496
        $dictionary = [];
497
498
        foreach ($results as $entity) {
499
            $wrapper = $this->factory->make($entity);
500
501
            $dictionary[$wrapper->getEntityAttribute('pivot')->$foreign][] = $entity;
502
        }
503
504
        return $dictionary;
505
    }
506
507
    /**
508
     * Get all of the IDs for the related models.
509
     *
510
     * @return array
511
     */
512
    public function getRelatedIds()
513
    {
514
        $fullKey = $this->relatedMap->getQualifiedKeyName();
515
516
        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...
517
    }
518
519
    /**
520
     * Update Pivot
521
     *
522
     * @param  \Analogue\ORM\Entity $entity
523
     * @return void
524
     */
525
    public function updatePivot($entity)
526
    {
527
        $keyName = $this->relatedMap->getKeyName();
528
529
        $this->updateExistingPivot(
530
            $entity->getEntityAttribute($keyName),
531
            $entity->getEntityAttribute('pivot')->getEntityAttributes()
532
        );
533
    }
534
535
    /**
536
     * Update Multiple pivot
537
     *
538
     * @param  $relatedEntities
539
     * @return void
540
     */
541
    public function updatePivots($relatedEntities)
542
    {
543
        foreach ($relatedEntities as $entity) {
544
            $this->updatePivot($entity);
545
        }
546
    }
547
548
    /**
549
     * Create Pivot Records
550
     *
551
     * @param \Analogue\ORM\Entity[] $relatedEntities
552
     * @return void
553
     */
554
    public function createPivots($relatedEntities)
555
    {
556
        $keys = [];
557
        $attributes = [];
558
559
        $keyName = $this->relatedMap->getKeyName();
560
561
        foreach ($relatedEntities as $entity) {
562
            $keys[] = $entity->getEntityAttribute($keyName);
563
        }
564
565
        $records = $this->createAttachRecords($keys, $attributes);
566
567
        $this->query->getQuery()->from($this->table)->insert($records);
568
    }
569
570
    /**
571
     * Update an existing pivot record on the table.
572
     *
573
     * @param  mixed $id
574
     * @param  array $attributes
575
     * @throws \InvalidArgumentException
576
     * @return integer
577
     */
578
    public function updateExistingPivot($id, array $attributes)
579
    {
580
        if (in_array($this->updatedAt(), $this->pivotColumns)) {
581
            $attributes = $this->setTimestampsOnAttach($attributes, true);
582
        }
583
584
        return $this->newPivotStatementForId($id)->update($attributes);
585
    }
586
587
    /**
588
     * Attach a model to the parent.
589
     *
590
     * @param  mixed $id
591
     * @param  array $attributes
592
     * @return void
593
     */
594
    public function attach($id, array $attributes = [])
595
    {
596
        $query = $this->newPivotStatement();
597
598
        $query->insert($this->createAttachRecords((array) $id, $attributes));
599
    }
600
601
    /**
602
     * @param  array $entities
603
     *
604
     * @throws \InvalidArgumentException
605
     */
606
    public function sync(array $entities)
607
    {
608
        $this->detachExcept($entities);
609
    }
610
611
    /**
612
     * Detach related entities that are not in $id
613
     *
614
     * @param  array $entities
615
     *
616
     * @throws \InvalidArgumentException
617
     *
618
     * @return void
619
     */
620
    protected function detachExcept(array $entities = [])
621
    {
622
        $query = $this->newPivotQuery();
623
624
        if (count($entities) > 0) {
625
            $keys = $this->getKeys($entities);
626
627
            $query->whereNotIn($this->otherKey, $keys);
628
        }
629
        $parentKey = $this->parentMap->getKeyName();
630
631
        $query->where($this->foreignKey, '=', $this->parent->getEntityAttribute($parentKey));
632
        
633
        $query->delete();
634
635
        $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...
636
    }
637
638
639
    /**
640
     * Create an array of records to insert into the pivot table.
641
     *
642
     * @param  array $ids
643
     * @param  array $attributes
644
     * @return array
645
     */
646
    protected function createAttachRecords($ids, array $attributes)
647
    {
648
        $records = [];
649
650
        $timed = in_array($this->createdAt(), $this->pivotColumns);
651
652
        // To create the attachment records, we will simply spin through the IDs given
653
        // and create a new record to insert for each ID. Each ID may actually be a
654
        // key in the array, with extra attributes to be placed in other columns.
655
        foreach ($ids as $key => $value) {
656
            $records[] = $this->attacher($key, $value, $attributes, $timed);
657
        }
658
        
659
        return $records;
660
    }
661
662
    /**
663
     * Create a full attachment record payload.
664
     *
665
     * @param  int   $key
666
     * @param  mixed $value
667
     * @param  array $attributes
668
     * @param  bool  $timed
669
     * @return array
670
     */
671
    protected function attacher($key, $value, $attributes, $timed)
672
    {
673
        list($id, $extra) = $this->getAttachId($key, $value, $attributes);
674
        
675
        // To create the attachment records, we will simply spin through the IDs given
676
        // and create a new record to insert for each ID. Each ID may actually be a
677
        // key in the array, with extra attributes to be placed in other columns.
678
        $record = $this->createAttachRecord($id, $timed);
679
680
        return array_merge($record, $extra);
681
    }
682
683
    /**
684
     * Get the attach record ID and extra attributes.
685
     *
686
     * @param  int   $key
687
     * @param  mixed $value
688
     * @param  array $attributes
689
     * @return array
690
     */
691
    protected function getAttachId($key, $value, array $attributes)
692
    {
693
        if (is_array($value)) {
694
            return [$key, array_merge($value, $attributes)];
695
        }
696
697
        return [$value, $attributes];
698
    }
699
700
    /**
701
     * Create a new pivot attachment record.
702
     *
703
     * @param  int  $id
704
     * @param  bool $timed
705
     * @return array
706
     */
707
    protected function createAttachRecord($id, $timed)
708
    {
709
        $parentKey = $this->parentMap->getKeyName();
710
711
        $record = [];
712
713
        $record[$this->foreignKey] = $this->parent->getEntityAttribute($parentKey);
714
715
        $record[$this->otherKey] = $id;
716
717
        // If the record needs to have creation and update timestamps, we will make
718
        // them by calling the parent model's "freshTimestamp" method which will
719
        // provide us with a fresh timestamp in this model's preferred format.
720
        if ($timed) {
721
            $record = $this->setTimestampsOnAttach($record);
722
        }
723
724
        return $record;
725
    }
726
727
    /**
728
     * Set the creation and update timestamps on an attach record.
729
     *
730
     * @param  array $record
731
     * @param  bool  $exists
732
     * @return array
733
     */
734
    protected function setTimestampsOnAttach(array $record, $exists = false)
735
    {
736
        $fresh = $this->freshTimestamp();
737
738
        if (!$exists) {
739
            $record[$this->createdAt()] = $fresh;
740
        }
741
742
        $record[$this->updatedAt()] = $fresh;
743
744
        return $record;
745
    }
746
747
    /**
748
     * @param EntityCollection $entities
749
     * @return array
750
     */
751
    protected function getModelKeysFromCollection(EntityCollection $entities)
752
    {
753
        $keyName = $this->relatedMap->getKeyName();
754
755
        return array_map(function ($m) use ($keyName) {
756
            return $m->$keyName;
757
        }, $entities);
758
    }
759
760
    /**
761
     * Detach models from the relationship.
762
     *
763
     * @param  int|array $ids
764
     * @throws \InvalidArgumentException
765
     * @return int
766
     */
767
    public function detach($ids = [])
768
    {
769
        if ($ids instanceof EntityCollection) {
770
            $ids = (array) $ids->modelKeys();
771
        }
772
773
        $query = $this->newPivotQuery();
774
775
        // If associated IDs were passed to the method we will only delete those
776
        // associations, otherwise all of the association ties will be broken.
777
        // We'll return the numbers of affected rows when we do the deletes.
778
        $ids = (array) $ids;
779
780
        if (count($ids) > 0) {
781
            $query->whereIn($this->otherKey, (array) $ids);
782
        }
783
784
        // Once we have all of the conditions set on the statement, we are ready
785
        // to run the delete on the pivot table. Then, if the touch parameter
786
        // is true, we will go ahead and touch all related models to sync.
787
        return $query->delete();
788
    }
789
    
790
    /**
791
     * Create a new query builder for the pivot table.
792
     *
793
     * @throws \InvalidArgumentException
794
     *
795
     * @return \Illuminate\Database\Query\Builder
796
     */
797
    protected function newPivotQuery()
798
    {
799
        $query = $this->newPivotStatement();
800
801
        $parentKey = $this->parentMap->getKeyName();
802
803
        return $query->where($this->foreignKey, $this->parent->getEntityAttribute($parentKey));
804
    }
805
806
    /**
807
     * Get a new plain query builder for the pivot table.
808
     *
809
     * @return \Illuminate\Database\Query\Builder
810
     */
811
    public function newPivotStatement()
812
    {
813
        return $this->query->getQuery()->newQuery()->from($this->table);
814
    }
815
816
    /**
817
     * Get a new pivot statement for a given "other" ID.
818
     *
819
     * @param  mixed $id
820
     *
821
     * @throws \InvalidArgumentException
822
     *
823
     * @return \Illuminate\Database\Query\Builder
824
     */
825
    public function newPivotStatementForId($id)
826
    {
827
        $pivot = $this->newPivotStatement();
828
829
        $parentKeyName = $this->parentMap->getKeyName();
830
831
        $key = $this->parent->getEntityAttribute($parentKeyName);
832
833
        return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id);
834
    }
835
836
    /**
837
     * Create a new pivot model instance.
838
     *
839
     * @param  array $attributes
840
     * @param  bool  $exists
841
     * @return \Analogue\ORM\Relationships\Pivot
842
     */
843
    public function newPivot(array $attributes = [], $exists = false)
844
    {
845
        $pivot = new Pivot($this->parent, $this->parentMap, $attributes, $this->table, $exists);
846
        
847
        return $pivot->setPivotKeys($this->foreignKey, $this->otherKey);
848
    }
849
850
    /**
851
     * Create a new existing pivot model instance.
852
     *
853
     * @param  array $attributes
854
     * @return \Analogue\ORM\Relationships\Pivot
855
     */
856
    public function newExistingPivot(array $attributes = [])
857
    {
858
        return $this->newPivot($attributes, true);
859
    }
860
861
    /**
862
     * Set the columns on the pivot table to retrieve.
863
     *
864
     * @param  array $columns
865
     * @return $this
866
     */
867
    public function withPivot($columns)
868
    {
869
        $columns = is_array($columns) ? $columns : func_get_args();
870
871
        $this->pivotColumns = array_merge($this->pivotColumns, $columns);
872
873
        return $this;
874
    }
875
876
    /**
877
     * Specify that the pivot table has creation and update timestamps.
878
     *
879
     * @param  mixed $createdAt
880
     * @param  mixed $updatedAt
881
     * @return \Analogue\ORM\Relationships\BelongsToMany
882
     */
883
    public function withTimestamps($createdAt = null, $updatedAt = null)
884
    {
885
        return $this->withPivot($createdAt ?: $this->createdAt(), $updatedAt ?: $this->updatedAt());
886
    }
887
888
    /**
889
     * Get the key for comparing against the parent key in "has" query.
890
     *
891
     * @return string
892
     */
893
    public function getHasCompareKey()
894
    {
895
        return $this->getForeignKey();
896
    }
897
898
    /**
899
     * Get the fully qualified foreign key for the relation.
900
     *
901
     * @return string
902
     */
903
    public function getForeignKey()
904
    {
905
        return $this->table . '.' . $this->foreignKey;
906
    }
907
908
    /**
909
     * Get the fully qualified "other key" for the relation.
910
     *
911
     * @return string
912
     */
913
    public function getOtherKey()
914
    {
915
        return $this->table . '.' . $this->otherKey;
916
    }
917
918
    /**
919
     * Get the fully qualified parent key name.
920
     *
921
     * @return string
922
     */
923
    protected function getQualifiedParentKeyName()
924
    {
925
        return $this->parentMap->getQualifiedKeyName();
926
    }
927
928
    /**
929
     * Get the intermediate table for the relationship.
930
     *
931
     * @return string
932
     */
933
    public function getTable()
934
    {
935
        return $this->table;
936
    }
937
938
    /**
939
     * Get the relationship name for the relationship.
940
     *
941
     * @return string
942
     */
943
    public function getRelationName()
944
    {
945
        return $this->relationName;
946
    }
947
}
948