Notes from the Team

Account for Nullable Fields in Laravel

Laravel Database

Laravel coding on a laptop

In Laravel, you will sometimes need to define a relationship between two models that is optional. We want to use foreign keys to reduce our workload down the road, without the extra work when creating or updating our models. We can have our cake and eat it too by implementing the following code:

namespace App\Traits;
trait NullableForeignKey {
    public static function bootNullableForeignKey() {
        // observe whenever we insert or update
        static::saving(function ($model) {
            // check they even have any keys set
            if(isset($model->nullableForeignKeys)) {
                foreach($model->nullableForeignKeys as &$key) {
                    // if the input coming in is false-y, set it to null
                    if(!$model->{$key}) {
                        $model->{$key} = null;
                    }
                }
            }
        });
    }
}

Then, simply apply the NullableForeignKey trait to the model like so:

namespace App\Models;
use App\Traits\NullableForeignKey;

class User {
    use NullableForeignKey;
    protected $nullableForeignKeys = ['foreign_key'];
}

That’s it! But to be thorough, let’s run through an example…

Let’s use a Terrible Car Analogy. Imagine that we have a Cars model and a Garage model. Not all cars are kept in garages, but sometimes a garage can have multiple cars housed in it. The migrations might look something like this in Laravel 5 (simplified for brevity):

class CreateGaragesTable extends Migration {
    public function up() {
        Schema::create(‘garages’, function (Blueprint $table) {
            $table->increments(‘garage_id’);
            $table->string(‘name’);
            $table->timestamps();
        });
    }

    public function down() {
        Schema::drop(‘garages’);
    }
}
class CreateCarsTable extends Migration {
    public function up() {
        Schema::create(‘cars’, function (Blueprint $table) {
            $table->increments(‘car_id’);
            $table->unsignedInteger(‘garage_id’)->nullable();
            $table->string(‘name’);
            $table->timestamps();
            $table->foreign(‘garage_id’)->references(‘garage_id’)->on(‘garages’)->onUpdate(‘cascade’)->onDelete(‘set null’);
        });
    }

    public function down() {
        Schema::drop(‘cars’);
    }
}

And our models might look something like this (again, keeping them simple for brevity).

namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Garage extends Model {
    protected $table = ‘garages’;
    protected $primaryKey = ‘garage_id’;
    protected $fillable = [‘name’];

    public function cars() {
        return $this->hasMany(‘App\Models\Car’, ‘garage_id’, ‘garage_id’);
    }
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\NullableForeignKey;

class Car extends Model {
    use NullableForeignKey;
    protected $nullableForeignKeys = [‘garage_id’];

    protected $table = ‘cars’;
    protected $primaryKey = ‘car_id’;
    protected $fillable = [‘name’, ‘garage_id’];

    public function garage() {
        return $this->belongsTo(‘App\Models\Garage’, ‘garage_id’, ‘garage_id’);
    }
}

Rather than write out an entire form and template, we will focus on the part that will be handling the selection of garage…

{!! Form::select(‘garage_id’, [” => ‘- Select One -‘] + Garage::lists(‘name’, ‘garage_id’)->toArray()) !!}

This will make a <select> populated by all the garages in the database, plus one option at the very beginning that have an empty value. Note that the name of the select will be the same as the string we passed into our $nullableForeignKeys variable in the Cars model.

Now, when we perform a save action (or a create or an update) the NullableForeignKey trait will see the matching input name, and turn the false-y empty string into a proper null value before saving it in the database. You could also use a value of 0 for the empty option, if you felt so inclined.

About The Author

Brian Harris
Brian Harris

President & Co-Founder

The Next Step

Connect

[email protected] (804) 272-1200