Don't take my word, I'm not sure of my solution, but I was able to build a custom relation which extends MorphToMany. I also feel like I am overcomplicating this, but I would like to get some other opinion. Here is the implementation, (I've only overwrote the attach cause is the only one I needed):
<?php
namespace App\Models\Relations;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class MultiMorphToMany extends MorphToMany
{
/**
* Attach models to the parent with custom attributes.
*
* @param mixed $ids
* @param array $attributes
* @param bool $touch
* @return void
*/
public function attach($id, array $attributes = [], $touch = true)
{
// Automatically set the resource_type attribute
$attributes['resource_type'] = $this->parent->getMorphClass();
parent::attach($id, $attributes, $touch);
}
}
<?php
namespace App\Models\Traits;
use App\Models\Relations\MultiMorphToMany;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Str;
trait HasMultiMorphToMany
{
/**
* Define a custom morphedByMany relationship.
*
* @param string $related
* @param string $parent The parent model class. Example: 'resource' for InvestorUpdate
* @param string $name The name of the relationship. Example: 'accessors' for InvestorUpdate
* @param string $table The name of the pivot table. Example: 'accesses'
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentPivotType The name of the column that holds the parent model's morph class. Example: 'resource_type' for InvestorUpdate
* @param string $parentKey
* @param string $relatedKey
* @return MultiMorphToMany
*/
public function multiMorphToMany($related, $parent, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentPivotType = null, $parentKey = null, $relatedKey = null, $relation = null)
{
$relation = $relation ?: $this->guessBelongsToManyRelation();
// First, we will need to determine the foreign key and "other key" for the
// relationship. Once we have determined the keys we will make the query
// instances, as well as the relationship instances we need for these.
$instance = $this->newRelatedInstance($related);
$foreignPivotKey = $foreignPivotKey ?: $name.'_id';
$relatedPivotKey = $relatedPivotKey ?: $instance->getForeignKey();
$parentPivotType = $parentPivotType ?: $parent . '_type';
// Now we're ready to create a new query builder for the related model and
// the relationship instances for this relation. This relation will set
// appropriate query constraints then entirely manage the hydrations.
if (! $table) {
$words = preg_split('/(_)/u', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
$lastWord = array_pop($words);
$table = implode('', $words).Str::plural($lastWord);
}
/** @var Builder $query */
$query = $instance->newQuery();
$query->where($table . '.' . $parentPivotType, $this->getMorphClass());
return $this->newMultiMorphToMany(
$query, $this, $name, $table,
$foreignPivotKey, $relatedPivotKey, $parentKey ?: $this->getKeyName(),
$relatedKey ?: $instance->getKeyName(), $relation, true
);
}
/**
* Instantiate a new CustomMorphToMany relationship.
*
* @param Builder $query
* @param Model $parent
* @param string $name
* @param string $table
* @param string $foreignPivotKey
* @param string $relatedPivotKey
* @param string $parentKey
* @param string $relatedKey
* @param string|null $relationName
* @param bool $inverse
* @return MultiMorphToMany
*/
protected function newMultiMorphToMany(Builder $query, Model $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName, $inverse): MultiMorphToMany
{
return new MultiMorphToMany(
$query, $parent, $name, $table, $foreignPivotKey, $relatedPivotKey, $parentKey, $relatedKey, $relationName, $inverse
);
}
}
Example usage:
public function podcasts(): MultiMorphToMany
{
return $this->multiMorphToMany(
Podcast::class,
'model',
'relatable',
'relatables'
);
}