0
votes

I have multiple Model classes that utilize a HasRetirements trait class. Both models use a MorphMany relationship to target the associated retirements table model for each model. Inside the HasRetirements trait class, I also have a isRetired() method as well as a currentRetirement() method. These methods are shown below.

I have come across a macro that can be chained onto an Eloquent relationship so that you can retrieve a single record. The macro toHasOne() utilizes model relationships through a hasMany relationship however my question is could this also be used for a morphMany relationship since it's polymorphic.

https://scotch.io/tutorials/understanding-and-using-laravel-eloquent-macros

public function currentRetirement()
{
    return $this->retirements()->whereNull('ended_at')->latest()->toHasOne();
}

public function isRetired()
{
    return $this->retirements()->whereNull('ended_at')->exists();
}
1

1 Answers

0
votes

With Laravel 5.5, you could register a macro returning a derived class from the BelongsToMany relation. This derived class also could be an anonymous class if you are not planning on using it anywhere else. Within the derived class, you need to override the match method and return the single object as a relation or null otherwise

BelongsToMany::macro('asSingleEntity', function() {

        return new class(
            $this->related->newQuery(),
            $this->parent,
            $this->table,        
            $this->foreignPivotKey,
            $this->relatedPivotKey,
            $this->parentKey,
            $this->relatedKey,
            $this->relationName) extends BelongsToMany {

            /**
             * Match the eagerly loaded results to their parents.
             *
             * @param  array   $models
             * @param  \Illuminate\Database\Eloquent\Collection  $results
             * @param  string  $relation
             * @return array
             */
            public function match(array $models, Collection $results, $relation)
            {
                $dictionary = $this->buildDictionary($results);

                // Once we have an array dictionary of child objects we can easily match the
                // children back to their parent using the dictionary and the keys on the
                // the parent models. Then we will return the hydrated models back out.
                foreach ($models as $model) {
                    if (isset($dictionary[$key = $model->{$this->parentKey}])) {
                        $model->setRelation(
                            // $relation, $this->related->newCollection($dictionary[$key])      // original code
                            $relation, array_first($dictionary[$key])
                        );
                    } else {
                        $model->setRelation($relation, null);
                    }
                }

                return $models;
            }

        };

    });

Then, you could simply use it within the model.

return $this
        ->belongsToMany(\App\Models\Entity::class, 'pivot_table_name')
        ->asSingleEntity();