spatie/laravel-morph-map-generator
Automatically generates and keeps Laravel Eloquent morph maps up to date. Models register themselves by implementing getMorphClass(), so you don’t forget to add entries. Optional auto-generation on app boot, configurable for custom model locations.
Installation:
composer require spatie/laravel-morph-map-generator
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\MorphMapGenerator\MorphMapGeneratorServiceProvider"
First Use Case:
Implement getMorphClass() in your Eloquent models:
// app/Models/Post.php
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function getMorphClass(): string
{
return 'post'; // Morph map key
}
}
The package auto-registers the model in Laravel's morph map during bootstrapping.
Verify:
dd(app('morphMap')->get('post')); // Should return Post::class
Model Registration:
getMorphClass() is auto-registered when the MorphMapGeneratorServiceProvider boots.MorphMap::macro() calls needed.Dynamic Morph Maps:
// Polymorphic relationship
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
Integration with Existing Code:
'posts') with dynamic calls:
// Before (manual)
MorphMap::macro('post', Post::class);
// After (auto-generated)
$morphKey = 'post'; // From Post::getMorphClass()
Testing:
$this->app->singleton('morphMap', fn() => new MorphMap([
'test' => TestModel::class,
]));
Conditional Morph Keys:
Use getMorphClass() logic to return dynamic keys (e.g., based on model state):
public function getMorphClass(): string
{
return $this->isPublished() ? 'published_post' : 'draft_post';
}
Bulk Registration: Register multiple models at once via a trait:
trait RegistersMorphMap
{
protected static function bootRegistersMorphMap()
{
if (static::getMorphClass()) {
app('morphMap')->macro(static::getMorphClass(), static::class);
}
}
}
Overriding Default Behavior: Extend the service provider to customize registration:
// app/Providers/MorphMapServiceProvider.php
use Spatie\MorphMapGenerator\MorphMapGeneratorServiceProvider as BaseProvider;
class MorphMapServiceProvider extends BaseProvider
{
public function register()
{
parent::register();
// Custom logic here
}
}
Missing getMorphClass():
dd(app('morphMap')->getAllMappings());
Case Sensitivity:
// ❌ Fails if morph key is 'Post' but method returns 'post'
public function getMorphClass(): string { return 'Post'; }
Caching Issues:
php artisan config:clear
Service Provider Boot Order:
MorphMapGeneratorServiceProvider loads before other providers that rely on morph maps (e.g., Spatie Media Library).dd(app('morphMap')->getAllMappings());
booted event listener to verify:
Model::booted(function () {
if (method_exists($this, 'getMorphClass')) {
dd('Morph key registered:', $this->getMorphClass());
}
});
Consistent Naming:
Use kebab-case for morph keys (e.g., blog-post) to avoid conflicts with reserved words.
Testing: Reset the morph map in tests:
$this->app->singleton('morphMap', fn() => new MorphMap());
Performance: The package adds minimal overhead (~1ms per request). Benchmark if using in high-frequency loops.
Extending:
Add custom logic via the registering event:
MorphMapGenerator::registering(function (Model $model) {
if ($model instanceof AdminPost) {
$model->setMorphClass('admin-post');
}
});
Legacy Code:
For models without getMorphClass(), manually register them in AppServiceProvider:
MorphMap::macro('legacy_model', LegacyModel::class);
How can I help you explore Laravel packages today?