A safer way to check model strictness in production

Without annoying your users

Joel Clermont
Joel Clermont
2024-02-06

In our projects, we like to enforce model strictness checks as a way of catching bugs or even potential security issues.

There are three different checks, and even though it's no longer documented, you can enforce all of them with a single call.

// app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::shouldBeStrict(config('app.env') === 'testing');
}

The boolean passed into shouldBeStrict is a flag to enable or disable strictness checks. When it's enabled, the checks will throw an exception if they fail.

I'm perfectly fine with that when running tests, but I don't want to throw an exception in production. So we only enable the checks in specific non-prod environments.

After being surprised that some devs are willing to throw exceptions in prod, it got me thinking. It would actually be helpful to know if these strictness checks were failing in prod. Could we log it instead of throwing an exception?

Yes, Laravel makes this pretty easy, in fact:

Model::shouldBeStrict();

if (config('app.env') === 'production') {
    $strictnessViolationCallback = function (Model $model, mixed $value) {
        // log it, report to Sentry, complain in Slack, etc.
    };
    Model::handleLazyLoadingViolationUsing($strictnessViolationCallback);
    Model::handleDiscardedAttributeViolationUsing($strictnessViolationCallback);
    Model::handleMissingAttributeViolationUsing($strictnessViolationCallback);
}

With this change, we now always enforce strictness checks, but if we're in production, we log the violations instead of throwing an exception.

Laravel gives us a way to register a callback. You could create three different callbacks if you wanted, but I think this approach is simpler.

Depending on which strictness check fails, $value will be different things in your callback:

  • Lazy loading: the relationship name
  • Discarded attribute: an array of attribute names
  • Missing attribute: the attribute name

I think this is the best of both worlds!

Here to help,

Joel

P.S. Want some help making your Laravel app more strict? Let's pair on it, and we can help you find and squash some bugs.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.