Working with null objects in Laravel

Null Object Pattern UML

Before starting the post I would like to say I’m not a “design patterns guru” or something like that. I’m just a passionate developer who loves clean code and elegant solutions, I come across a lot of issues every day and looking for solutions to share with the community is my way to deal with it. If you think my approach is not the best to deal with null return types or want to add something to my post, you can reach me on Twitter and I will be happy to start a polite discussion with you.

Intro

The problem: many times we don’t want to break the normal flow of the application even if we got a null instead of an expected object at some point.

Solution: use a Null object or Special Case. These are techniques defined by Martin Fowler in his books Refactoring: Improving the design of existing code and Patterns of enterprise application.

But, what is a “null object”? Excellent question! A null object is an object that shares the same interface as another one but defines default values in it, so you can return it instead of a null value and don’t break the flow of your application.

Using null objects in Laravel

To demonstrate the usage of null objects we will be building a demo blog app, let’s do it.

Setting up the project

First we need to create the project:

composer create-project --prefer-dist laravel/laravel blog

Then configure the database in your .env file, I will be using SQLite but you can use whatever database engine you want.

DB_CONNECTION=sqlite
DB_FOREIGN_KEYS=false

Don’t forget to create a file called database.sqlite in the database directory, and remove the DB_DATABASE key in your .env file.

We will use the default Laravel authentication, so run the following command in the root of your project.

php artisan make:auth

Now we will define the necessary models, controllers, factories, and migrations, by running the following commands.

php artisan make:model Post -a
php artisan make:controller UserController -m User

The migration for the posts table should look like this:

class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->text('body');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')
->references('id')
->on('users');
$table->timestamps();
});
}
}

Laravel provides a User model by default, as well as a factory and migration, so we don’t have to define it.

Note that we are not defining the behavior of the foreign keys on delete, this is an essential part of the process of building this application.

Now we will define the relationship between models.

class User extends Authenticatable
{
public function posts()
{
return $this->hasMany(Post::class);
}
}

class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}

Next we will prepare the database to be seeded, first let’s define the PostFactory.

use Faker\Generator as Faker;
$factory->define(App\Post::class, function (Faker $faker) {
return [
'title' => $faker->sentence,
'body' => $faker->paragraphs(5, true)
];
});

Now edit the database/DatabaseSeeder.php file to look like this

class DatabaseSeeder extends Seeder
{
public function run()
{
factory(User::class, 10)->create()->each(function ($user) {
$user->posts()->saveMany(factory(Post::class, 3)->make());
});
}
}

Finally you can run the following command and all tables in the database will be created and populated.

php artisan migrate --seed

With this last step, the setup is done, now let’s start working.

Showing the data

After finishing the setup we are ready to start showing the data, we need to define some routes, views, and controller actions, let’s do it!

## routes/web.php
Route::resource('/posts', 'PostController')->middleware('auth');
Route::resource('/users', 'UserController')->middleware('auth');

class PostController extends Controller
{
public function index()
{
return view('posts.index')->with('posts', Post::paginate());
}
    public function show(Post $post)
{
return view('posts.show')->with('post', $post);
}
}

class UserController extends Controller
{
public function show(User $user)
{
return view('users.show')->with('user', $user);
}
}

## posts/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
@foreach($posts as $post)
<div class="card" style="margin-bottom: 1%">
<div class="card-body">
<h5 class="card-title"><a href="{{ route('posts.show', $post->id) }}">{{ $post->title }}</a></h5>
<p>Posted by: <a href="{{ route('users.show', $post->user->id)}}" class="card-link">{{ $post->user->name }}</a></p>
</div>
</div>
@endforeach
            {{ $posts->links() }}
</div>
</div>
</div>
@endsection

## posts/show.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<h1 class="card-title">{{ $post->title }}</h1>
<p class="card-text">
{{ $post->body }}
</p>
                    <p>Posted by: <a href="{{ route('users.show', $post->user->id)}}" class="card-link">{{ $post->user->name }}</a></p>
</div>
</div>
</div>
</div>
</div>
@endsection

## users/show.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<p class="card-text">
<b>Name:</b> {{ $user->name }}
</p>
<p class="card-text">
<b>Email:</b> {{ $user->email }}
</p>
</div>
</div>
</div>
</div>
</div>
@endsection

Log in the tinker console and use the email from one of your users, and the word “password” as the password to logging in to the application.

php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.15-0ubuntu0.18.04.1 — cli) by Justin Hileman
>>> App\User::find(2);
=> App\User {#2945
id: "2",
name: "Coby Lakin Sr.",
email: "myrl25@example.org",
email_verified_at: "2019-03-24 14:59:03",
created_at: "2019-03-24 14:59:03",
updated_at: "2019-03-24 14:59:03",
}

At this point we can view a list of posts, an individual post, and an individual user by following the link in a specific post.

Now, what would happen if we delete a user? We didn’t define a cascade behavior on delete, and we don’t want that, we want the possibility of deleting a user but keep his/her posts, all this without breaking the application when someone follows the link to the user.

Introducing null objects

First of all we will delete the user with id 1, that way we can see how the application will blow up in one hundred pieces. We can achieve this using tinker, so we don’t need to write the functionality.

php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.15-0ubuntu0.18.04.1 — cli) by Justin Hileman
>>> App\User::destroy(1);

Now the application is broken and if you try to visit the /posts URL, you should see a message like “Trying to get property ‘id’ of non-object”, that is because there not exists a model for a User with id 1. We are going to introduce a null object for these cases.

Creating our first null object

As we said before, null objects should share the same interface as the real object, that’s the reason why our null object will inherit from the User class.

namespace App;
class MissingUser extends User
{
protected static $unguarded = true;
    protected $attributes = [
'name' => 'missing name',
'email' => 'missing email'
];
}

With this we are essentially creating a User model with a default name and email, note that we are not adding an id, we will address this shortly, now let’s use our null object in the relationship defined in the Post model, modify the user relationship to look like this.

class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class)->withDefault(
MissingUser::make(['id' => $this->user_id])->toArray()
);
}

As you can see we are adding an id property to the MissingUser model with the user_id set in the Post model.

At this point you should visit the /posts URL and see “Posted by: missing name” in the posts that belong to the deleted user, but the application is still working as expected. The only problem right now is that if you follow the link to the see the user information the application will respond with a 404, we don’t want that, we want to know the user is missing but we don’t want exceptional behavior, so let’s modify our UserController to return a MissingUser instead of a 404.

class UserController extends Controller
{
public function show(int $id)
{
return view('users.show')->with('user', User::find($id) ?? MissingUser::make());
}
}

You can even create a helper method in the User class, so you don’t have to check if find returns null every single time.

class User extends Authenticatable
{
public static function findOrMissing($id)
{
return self::find($id) ?? MissingUser::make();
}
}

And change the call in the controller

class UserController extends Controller
{
public function show(int $id)
{
return view('users.show')->with('user', User::findOrMissing($id));
}
}

Now our application is fully functional without the need of deleting our records in cascade, or using a lot of guard clauses to verify the existence of an object, pretty neat!

Final thoughts

Laravel encourages developers to use null objects in models via the $attributes property as well as in relationships via the withDefault method, what I made here is just use a more traditional approach by defining a new class, but you can implement the same behavior without defining a custom class, I personally prefer to be as explicit as possible, but I could rewrite the whole article without the need for a custom class.

Post-credits

As always the application built in here is just for demonstration purposes, in a real application many things should be done differently. Also I’m not suggesting you should keep child rows without a parent in your database just to keep the application working as expected 100% of the time, as I said before this is just a demo, you should decide by yourself when to use this technique in your projects, I personally find myself using it frequently.

If you want to learn more about this and other techniques I strongly recommend the book Refactoring Improving the Design of Existing Code by Martin Fowler, who by the way launched the second edition of the book some months ago, now with the code samples written in Javascript.

This pattern is also referenced in the book Patterns of Enterprise Application Architecture by the same author. I’m unfamiliar with the book, so I can’t tell the way the pattern is approached on it.

I would love to read your thoughts about the article, don’t hesitate to ping me on Twitter if you want to add something, discuss the article, or even say hi.

You can find the demo application on Github, just in case you’re curious about it.

I hope you have found this article useful. Thanks for staying this long!

Originally posted on devalmonte.com