Last active
January 29, 2020 22:52
-
-
Save goodevilgenius/c2b04851f8d8aaeeb83bd501c103e692 to your computer and use it in GitHub Desktop.
Inverse of Jensseger's BelongsToMany mongodb relationship: https://github.com/jenssegers/laravel-mongodb/blob/master/src/Jenssegers/Mongodb/Relations/BelongsToMany.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
namespace App\Models\Relations; | |
use Illuminate\Database\Eloquent\Builder; | |
use Illuminate\Database\Eloquent\Collection; | |
use Illuminate\Database\Eloquent\Model; | |
use Jenssegers\Mongodb\Relations\BelongsToMany; | |
/** | |
* This is the inverse of the BelongsToMany relationship. | |
* | |
* Unlike with SQL models, on NoSQL models, the BelongsToMany relationship does not use a pivot table. | |
* Instead, an array of keys is added to the "owning" model. Because of this, BelongsToMany is one way only. | |
* | |
* This class implements the reverse of this relationship. Therefore, the parent model will hold an array of keys | |
* indicating the child models. | |
* @mixin Builder | |
*/ | |
class OwnsMany extends BelongsToMany | |
{ | |
protected static $constraints = true; | |
/** | |
* {@inheritdoc} | |
*/ | |
public function addConstraints(): void | |
{ | |
$this->query->whereIn($this->relatedKey, $this->parent->{$this->foreignPivotKey} ?: []); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function addEagerConstraints(array $models): void | |
{ | |
$ids = array_flatten($this->getKeys($models, $this->foreignPivotKey)); | |
$this->query->orWhereIn($this->relatedKey, $ids); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function match(array $models, Collection $results, $relation) | |
{ | |
foreach ($models as $model) { | |
$ids = $model->{$this->foreignPivotKey}; | |
$related = $results->filter(function (Model $model) use ($ids) { | |
return array_search($model->{$this->relatedKey}, $ids) !== false; | |
}); | |
$related = $this->orderCollection($related, $ids); | |
$model->setRelation($relation, $related->values()); | |
} | |
return $models; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function attach($id, array $attributes = [], $touch = true) | |
{ | |
$ids = $this->parseIds($id); | |
if (!$this->fireParentEvent('attaching', true, $ids)) { | |
return; | |
} | |
$current = $this->parent->{$this->foreignPivotKey}; | |
if (empty($current) || !is_array($current)) { | |
$current = []; | |
} | |
array_push($current, ...$ids); | |
$current = array_values(array_unique($current)); | |
$this->parent->{$this->foreignPivotKey} = $current; | |
$this->parent->save(); | |
if ($touch) { | |
$this->touchIfTouching(); | |
} | |
$this->fireParentEvent('attached', false, $ids); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function detach($ids = null, $touch = true): int | |
{ | |
$ids = $this->parseIds($ids); | |
if (empty($ids)) { | |
return 0; | |
} | |
if (!$this->fireParentEvent('detaching', true, $ids)) { | |
return 0; | |
} | |
$current = $this->parent->{$this->foreignPivotKey}; | |
if (empty($current) || !is_array($current)) { | |
$current = []; | |
} | |
$new = array_values(array_diff($current, $ids)); | |
$this->parent->{$this->foreignPivotKey} = $new; | |
$this->parent->save(); | |
if ($touch) { | |
$this->touchIfTouching(); | |
} | |
$this->fireParentEvent('detached', false, $ids); | |
return count($current) - count($new); | |
} | |
/** | |
* Fires an event on the parent model. | |
* | |
* Note that this only fires events through the dispatcher. It cannot fire custom model events, i.e., those added to | |
* $dispatchesEvents in the model. | |
* | |
* @param string $event The name of the event | |
* @param bool $halt Whether to stop if the event listener returns false | |
* @param array $ids Model ids associated with the event | |
* @return mixed | |
*/ | |
protected function fireParentEvent(string $event, bool $halt = true, array $ids = []) | |
{ | |
$dispatcher = $this->parent->getEventDispatcher(); | |
if (is_null($dispatcher)) { | |
return true; | |
} | |
// First, we will get the proper method to call on the event dispatcher, and then we | |
// will attempt to fire a custom, object based event for the given event. If that | |
// returns a result we can return that result, or we'll call the string events. | |
$method = $halt ? 'until' : 'dispatch'; | |
$payload = ['model' => $this->parent, 'ids' => $ids]; | |
return $dispatcher->{$method}( | |
"eloquent.{$event}: " . get_class($this->parent), $payload | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getResults() | |
{ | |
// parent::getResults() doesn't work here because parent models without an ID can still have OwnsMany children. | |
$results = !empty($this->parent->{$this->foreignPivotKey}) ? $this->get() : $this->related->newCollection(); | |
if ($results instanceof Collection) { | |
return $this->orderCollection($results); | |
} | |
return $results; | |
} | |
/** | |
* Orders the Collection passed by the parent foreign key. | |
* | |
* @param Collection $collection | |
* @param null|array $ids | |
* @return Collection | |
*/ | |
protected function orderCollection(Collection $collection, $ids = null) | |
{ | |
$ids = $ids ?? $this->parent->{$this->foreignPivotKey}; | |
return $collection->sortBy(function (Model $model) use ($ids) { | |
return array_search($model->{$this->relatedKey}, $ids); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello! Thank you for your solution.
But.. how I can use this relationship class into my models?