Enforcing Morph Maps in Laravel

Published on by

Enforcing Morph Maps in Laravel image

Oliver Nybroe contributed a pull request to Laravel (8.59.0) that allows developers to require that morph maps to be set, rather than defaulting to fully qualified class names.

By using the enforceMorphMap method instead of the traditional morphMap method, Laravel will ensure that all morphs are mapped to an alias and throw a ClassMorphViolationException exception if one is not mapped.

// If any other models are morphed but not mapped, Laravel
// will throw a `ClassMorphViolationException` exception.
Relation::enforceMorphMap([
'user' => User::class,
]);

You can enforce a morph map using the single call shown above, or you can use the standalone requireMorphMap method on the Relation class:

// Turn morph map enforcement on (new in 8.59.0).
Relation::requireMorphMap();
 
// And then map your morphs in the standard way.
Relation::morphMap([
'user' => User::class,
]);

Morph Map Background

When creating polymorphic relationships in Laravel, the default behavior has always been to store the class name of the related model in the database. From the Laravel documentation:

By default, Laravel will use the fully qualified class name to store the "type" of the related model. For instance, given the one-to-many relationship example above where a Comment model may belong to a Post or a Video model, the default commentable_type would be either App\Models\Post or App\Models\Video, respectively.

Using this default method means that your database will end up populated with the class names of your models, which tightly couples the data in your database to the names of your classes.

Laravel has always given us the ability to decouple the class name from the database by registering a MorphMap which provides an alias for a class, breaking that association:

// Store `user` in the database, instead of `App\User`
Relation::morphMap([
'user' => User::class,
]);

While this behavior has been available for some time, it has never been possible to strictly require it.

Benefits of Morph Maps

The benefit of having a morph map in the first place is to decouple your application logic from your stored data. Storing class names in your database can lead to pernicious, hard to debug errors.

If you change the name of one of the classes that has been morphed, all of the references in your database will no longer line up with your application. This sneaky part about this is that it's likely that nothing will fail in development or testing! Unless your test suite somehow populates your database with the previous class name, all of your tests will be green. Only when you deploy to production will the class and data mismatch arise.

The scenario above could be solved by writing a migration to update your stored data, but there's no guarantee that you (or the next person, or the next) will remember that it's necessary.

Rather than relying on your company's institutional memory, morph maps break the association and free you from that potential bug.

Benefits of Enforcing A Morph Map

In the same way that morph maps free you from having to remember to update stored data when you refactor a class, requiring a morph map frees you from having to remember that you need to register a class in the first place.

Before morph maps were enforceable, it was incumbent on the developer or the team to remember to map morphs in the first place.

For example, one developer could set up a morph map like this:

Relation::morphMap([
'image' => Image::class,
'post' => Post::class,
]);

and then another developer could come along and start using App\Video as a morphed relation without adding it to the map!

Now, in the database, you'd see the following types:

  • image - from the morph map
  • post - from the morph map
  • App\Video - from the Laravel default implementation

Because the developer didn't know there was a morph map set up, they didn't register their new Video model. Given an old enough application or a large enough team, this is an inevitability.

Now by enforcing a morph map, the App\Video cannot be used in a morph unless an alias has been set up. A ClassMorphViolationException will be thrown.

// All future morphs *must* be mapped!
Relation::enforceMorphMap([
'image' => Image::class,
'post' => Post::class,
]);

This new feature allows you take what was implicit knowledge and make it an explicit code requirement.

The more explicit we can make the requirements of an application the easier it will be to maintain over time, and the easier it will be to bring on new developers.

Aaron Francis photo

Writing and tweeting about building products + Laravel.

Working on torchlight.dev & hammerstone.dev.

Dad to boy/girl twins.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project.

Visit No Compromises
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Anonymous Event Broadcasting in Laravel 11.5 image

Anonymous Event Broadcasting in Laravel 11.5

Read article
Microsoft Clarity Integration for Laravel image

Microsoft Clarity Integration for Laravel

Read article
Apply Dynamic Filters to Eloquent Models with the Filterable Package image

Apply Dynamic Filters to Eloquent Models with the Filterable Package

Read article
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article