Use different morph maps for different relationships on the same model.
By default, Laravel's Relation::morphMap() applies globally to all polymorphic relationships. This package lets you define a custom morph map per relationship, so different polymorphic relations on the same model can use different type aliases.
| Version | PHP | Laravel |
|---|---|---|
| 2.x | ^8.1 | 10, 11, 12 |
| 1.x | >=8.0 | 8, 9, 10, 11 |
composer require moneo/laravel-morphmapNo service provider registration is needed. Just use the trait.
Add the HasCustomMorphMap trait to your model and define the $customMorphMap property in your constructor.
The array keys are the related model's fully qualified class name, and the values are the morph type alias you want stored in the database for that relationship:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Moneo\LaravelMorphMap\Database\Eloquent\Concerns\HasCustomMorphMap;
class Post extends Model
{
use HasCustomMorphMap;
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
// Define custom morph types per related model.
// Key: related model class, Value: morph type alias
$this->customMorphMap = [
Category::class => 'post',
];
// Optional: override the default morph type for all other relationships.
// If not set, defaults to the fully qualified class name (static::class).
// $this->defaultMorphType = 'post';
}
/**
* Tags relationship — no custom mapping defined for Tag,
* so the default morph type (App\Models\Post) will be used.
*/
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
/**
* Categories relationship — custom mapping defined above,
* so 'post' will be stored as the morph type.
*/
public function categories(): MorphToMany
{
return $this->morphToMany(Category::class, 'categoryable');
}
}The trait also supports morphedByMany for inverse polymorphic many-to-many relationships:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Moneo\LaravelMorphMap\Database\Eloquent\Concerns\HasCustomMorphMap;
class Category extends Model
{
use HasCustomMorphMap;
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'categoryable');
}
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'categoryable');
}
}The trait uses Laravel's initializeHasCustomMorphMap() convention to automatically set the default morph type when the model is constructed. No need to call parent::__construct() before setting properties -- the trait handles initialization automatically.
When you call morphToMany() or morphedByMany(), the trait:
- Looks up the related model class in your
$customMorphMaparray - If found, registers that alias in Laravel's global morph map
- If not found, uses the
$defaultMorphType(which defaults to the model's FQCN) - Delegates to the parent relationship method
Suppose your taggables table uses fully qualified class names, but your categoryables table uses short aliases:
| tag_id | taggable_id | taggable_type |
|---|---|---|
| 1 | 1 | App\Models\Post |
| 2 | 1 | App\Models\Video |
| category_id | categoryable_id | categoryable_type |
|---|---|---|
| 1 | 1 | post |
| 2 | 1 | video |
With this package, both table structures work seamlessly on the same model.
composer testcomposer analyse# Check style
composer format:check
# Fix style
composer formatContributions are always welcome! Please see CONTRIBUTING.md for details.
Thanks to all of our contributors!
The MIT License (MIT). Please see License File for more information.
