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.

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

It’s potentially possible to fallback to a different object when optional value is null, but you have to be really careful about Eloquent active record behavior.

If you wanted to just get null for the phone number of an unauthenticated guest:

That unsaved User instance will end up returning the first row from anyone in the `phones` table since it’s making a query without constraints. i.e., SELECT * FROM phones LIMIT 1

Attempting optional access to nested properties more than one level deep likely leads to a null reference ErrorException. Nested optional(optional()) would be avoided as its whole intent is to make code more readable.

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

View the optional implementation