Skip to content

Instantly share code, notes, and snippets.

@goodevilgenius
Last active January 29, 2020 22:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goodevilgenius/c2b04851f8d8aaeeb83bd501c103e692 to your computer and use it in GitHub Desktop.
Save goodevilgenius/c2b04851f8d8aaeeb83bd501c103e692 to your computer and use it in GitHub Desktop.
<?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);
});
}
}
@Tizianoz93
Copy link

Hello! Thank you for your solution.
But.. how I can use this relationship class into my models?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment