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 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  | 
            ||
| 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)  | 
            ||
| 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')  | 
            ||
| 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)  | 
            ||
| 163 | |||
| 164 | /**  | 
            ||
| 165 | * Return Pivot attributes when available on a relationship  | 
            ||
| 166 | *  | 
            ||
| 167 | * @return array  | 
            ||
| 168 | */  | 
            ||
| 169 | public function getPivotAttributes()  | 
            ||
| 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);  | 
            ||
| 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 = ['*'])  | 
            ||
| 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); | 
            ||
| 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)  | 
            ||
| 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)  | 
            ||
| 324 |     { | 
            ||
| 325 |         $query->select(new Expression('count(*)')); | 
            ||
| 326 | |||
| 327 | $tablePrefix = $this->query->getQuery()->getConnection()->getTablePrefix();  | 
            ||
| 328 | |||
| 329 | $query->from($this->table . ' as ' . $tablePrefix . $hash = $this->getRelationCountHash());  | 
            ||
| 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()  | 
            ||
| 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)  | 
            ||
| 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)  | 
            |
| 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());  | 
            ||
| 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)  | 
            ||
| 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 id is empty, we'll simply skip that statement.  | 
            ||
| 625 |         if (count($entities) > 0) { | 
            ||
| 626 | $keys = $this->getKeys($entities);  | 
            ||
| 627 | |||
| 628 | $query->whereNotIn($this->otherKey, $keys);  | 
            ||
| 629 | }  | 
            ||
| 630 | |||
| 631 | $parentKey = $this->parentMap->getKeyName();  | 
            ||
| 632 | |||
| 633 | $query->where($this->foreignKey, '=', $this->parent->getEntityAttribute($parentKey));  | 
            ||
| 634 | |||
| 635 | $query->delete();  | 
            ||
| 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 = [])  | 
            ||
| 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()  | 
            ||
| 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 = [])  | 
            ||
| 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)  | 
            ||
| 887 | |||
| 888 | /**  | 
            ||
| 889 | * Get the key for comparing against the parent key in "has" query.  | 
            ||
| 890 | *  | 
            ||
| 891 | * @return string  | 
            ||
| 892 | */  | 
            ||
| 893 | public function getHasCompareKey()  | 
            ||
| 897 | |||
| 898 | /**  | 
            ||
| 899 | * Get the fully qualified foreign key for the relation.  | 
            ||
| 900 | *  | 
            ||
| 901 | * @return string  | 
            ||
| 902 | */  | 
            ||
| 903 | public function getForeignKey()  | 
            ||
| 907 | |||
| 908 | /**  | 
            ||
| 909 | * Get the fully qualified "other key" for the relation.  | 
            ||
| 910 | *  | 
            ||
| 911 | * @return string  | 
            ||
| 912 | */  | 
            ||
| 913 | public function getOtherKey()  | 
            ||
| 917 | |||
| 918 | /**  | 
            ||
| 919 | * Get the fully qualified parent key name.  | 
            ||
| 920 | *  | 
            ||
| 921 | * @return string  | 
            ||
| 922 | */  | 
            ||
| 923 | protected function getQualifiedParentKeyName()  | 
            ||
| 927 | |||
| 928 | /**  | 
            ||
| 929 | * Get the intermediate table for the relationship.  | 
            ||
| 930 | *  | 
            ||
| 931 | * @return string  | 
            ||
| 932 | */  | 
            ||
| 933 | public function getTable()  | 
            ||
| 937 | |||
| 938 | /**  | 
            ||
| 939 | * Get the relationship name for the relationship.  | 
            ||
| 940 | *  | 
            ||
| 941 | * @return string  | 
            ||
| 942 | */  | 
            ||
| 943 | public function getRelationName()  | 
            ||
| 947 | }  | 
            ||
| 948 | 
If you implement
__calland 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
__callis implemented by a parent class and only the child class knows which methods exist: