Michael Dyrynda
Home Blog Podcasts
 
Working with nullable fields in Eloquent models May 15th, 2015

Introduction

If you have a model or two and only a single nullable field in that model, creating a an mutator method for that one field is trivial:

1public function setNicknameAttribute($nickname)
2{
3 $this->attributes['nickname'] = trim($nickname) == '' ? null : trim($nickname);
4}

What this does is check that the supplied input, in this case $nickname, is an empty string. If so, we'll set the attribute to null, otherwise we'll set the trim'd $nickname .

Why am I using trim and not empty? Using trim will strip out any leading or trailing whitespace and compare against an empty string, to make sure that our input is indeed empty. This allows us make sure that we're not getting some wise guy trying to insert whitespace into our database. Using empty will consider any number of whitespace characters non-empty.

There has been more than one occasion where I've had nullable fields in my database and found myself writing the same code over and over in an attribute mutator in order to make sure that I'm actually putting a null field in the database if the input is empty. Surely there's a better way? In my experience, there's two, and which you choose will come down to personal preference - or you're environment if you're (un)lucky.

Using a base model

If you have multiple methods that you'd like to use in multiple Eloquent models, you might consider using a base model that your other classes extend from. This is useful if you're going to use the nullIfEmpty method at least once in every model that extends from it. Extending from a model and not using any of it's methods can add unnecessary overhead.

1<?php namespace App;
2 
3use Illuminate\Database\Eloquent\Model;
4 
5class BaseModel extends Model {
6 
7 public function __construct(array $attributes = array())
8 {
9 parent::__construct($attributes);
10 }
11 
12 
13 protected function nullIfEmpty($input)
14 {
15 return trim($input) == '' ? null : trim($input);
16 }
17 
18 
19}

This function will then be available to any model that extends our BaseModel:

1<?php namespace App;
2 
3class UserModel extends BaseModel {
4 
5 public function setNicknameAttribute($nickname)
6 {
7 $this->attributes['nickname'] = $this->nullIfEmpty($nickname);
8 }
9 
10 
11}

This option works, but it's not immediately obvious where the nullIfEmpty methods comes from. It won't take long to figure out where it comes from, but it's something that can be avoided.

For those of you still using PHP < 5.4.0, this will be your only option, however.

Using a trait

If you're using PHP >= 5.4.0, traits will provide you a nice declarative way to reuse this functionality across your models.

1<?php namespace App\Traits;
2 
3trait NullableFields {
4 
5 protected function nullIfEmpty($input)
6 {
7 return trim($input) == '' ? null : trim($input);
8 }
9 
10 
11}

This trait can then be used directly within your model:

1<?php namespace App;
2 
3use App\Traits\NullableFields;
4use Illuminate\Database\Eloquent\Model;
5 
6class UserModel extends Model {
7 
8 use NullableFields;
9 
10 public function setNicknameAttribute($nickname)
11 {
12 $this->attributes['nickname'] = $this->nullIfEmpty($nickname);
13 }
14 
15 
16}

Conclusion

Ultimately, the result is the same; you declare a function that performs the check of your input and returns either null or your trim'd input, which you assign to the appropriate attribute.

I don't personally think either method is particularly quicker than the other, and both can be reused between projects quite easily. At the end of the day, pick whichever you're most comfortable with. The trait is most declarative as you can see when NullableFields is being used, rather than looking into your BaseModel class.

In my opinion, the trait path is a better one as you can keep your BaseModel - if you still use it - quite lean. The BaseModel ought to be used for methods that will be used in every child model. If you find yourself putting every utility method into your BaseModel, you'll find it gets filled with loads of methods that might only be used in one or two models out of your entire model-base, adding unnecessary overhead.

I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight