optional() is now a helper function built into the Laravel 5.5 core.

A recent Laravel 5.4 addition inspired me to discover an expressive approach to handling PHP objects that may be null. When higher-order tap() appeared in Laravel’s commit history last week (see Taylor Otwell’s explanation), I immediately connected it to transform() that I proposed for conditionally changing primitive values.

I want to eliminate these goofy conditionals, fluff greyed out:

  • if (Auth::check() && Auth::user()->phone) {
  • {{ old(‘address_uuid’, $company->primaryAddress ?
        $company->primaryAddress->uuid : null) }}
  • return $this->latestDraft && $this->latestDraft->isPending();

The goals I have in mind are:

  • Reduce repetition of variables, property names, and class methods that make conditionals more resistant to change. For requirement adjustments, think of it as avoiding a programmer’s death by 1,000 cuts.
  • Allow expression of requirements in left-to-right statements of natural English, eliminating bleep-blorp speak.

So using a higher-order wrapper class, I came up with global helper function optional(). It’s a fluid and more succinct approach of calling an object method or accessing its properties when that instance may be null.

Here are some common examples in Laravel applications.

Formatting a nullable Eloquent date

Working with notifiable classes, the read_at attribute’s date mutator will give a Carbon object when a date is present in the database column. To show the duration since someone read their notification the Blade snippet may look like:

To make that view more concise and allow reuse of the conditional in other views, it can be implemented as an App\Models\Notification decorator method using model presenters (implemented using laracasts/presenter or hemp/presenter):

Well that overwrought solution just made me type “read” four times. Let’s just tell the view what we want in plain language:

old() default values from relationships

Many form layouts can share the responsibility of both create and edit actions for the given resource. When setting form input defaults, how do you handle values coming from relationships that may have not been be stored yet?

Here are three possible approaches to pre-filling a payment form’s billing address based on the logged in user’s history.

Ternary operator in the darkest timeline

If you’re on mobile, that ugly last line probably fell off your screen. These type of expressions are why I came up optional.

Default to a blank model, passed controller to view

Then the view becomes:

That view does read much nicer but now there’s a dependency to the controller. I prefer to pass as few variables as possible into view() to reduce cognitive overhead. And same as the notification timestamp example – I’m typing “address” how many times?

optional() simplifies it to a one-liner

Marvel at its beauty:

Saving Eloquent model for nullable foreign keys

Another use case is setting relationships when storing an Eloquent model. Laravel gives a nice fluent interface for defining one of the table relationships, but when that related model is related to other tables, ternary operators are usually the approach du jour.

Here is a processed order being marked as paid completely, either as a self-checkout through the online payment gateway ($payment exists) or an offline payment being OK’d by finance ($payment is null.) The reconciliation history entry can look like:

Model predicate method on a nullable relationship

HasOne and BelongsTo relationships resolving to a single Eloquent model instance are ideal for a terse optional.

Also keep in mind Laravel 5.4 added default models for the HasOne relation so you can try a null object instead of even using optional().

Conditional eager-loads

An application may have publicly-available pages showing additional components to admins, requiring more Eloquent with() eager-loads to avoid N+1 database queries.

optional limitation – good for one arrow operator

Like Collection proxy-enabled methods, you may only invoke a method or property one level deep on the object made optional().

optional(Auth::user())->phone->isNorthAmerican() would cause an ErrorException to occur. Guests make optional(Auth::user())->phone return null.

Nested optional(optional(Auth::user())->phone)->isNorthAmerican() should be avoided as the whole intent of this function is to make code more readable. That expression is a horrorshow. Not to mention the Law of Demeter overreach happening here!

So as a general rule of thumb look up the origin of that phrase, only use a single arrow operator off optional()

PHP 7’s null coalesce operator can also save the day

Without a method call in an expression, you can actually avoid optional() to access nested object properties. So {{ Auth::user()->phone->number ?? null }} silently echoes null even when Auth::user()->phone is null.

View the optional implementation