David Hemphill

Product Designer / Full Stack Developer / Entrepreneur

Presenters in Laravel

Sep 6, 2016

If you’ve been using Laravel on a project for a long enough time, chances are your Eloquent models have grown. Over time, these classes can become difficult to maintain as you pile on functionality. As your write logic for every context in which your models are used, it’s tempting to fatten our models to the point of obesity. While these obese models should be preferred to prematurely refactoring to a pattern, model obesity can have some real drawbacks on the health of your project. It’s dangerous to allow your models to drink ‘Logic Mountain Dew’ on the regular. Not in the sense it will cause you to go bankrupt, set off every nuke in the US, or broadcast our exact location to a malevolent alien species, but dangerous in the sense that it could be sort of difficult to deal with later on.

In these cases we can lean on the Decorator Pattern to allow us to refactor some of this functionality to a context-specific class, which can clean up our models quite a bit. For instance, we can use decorators to separate formatting for PDFs, CSV, or API responses.

What is a Decorator? What is a Presenter?

A Decorator is an object that wraps another object in order to add functionality to the wrapped object. It also delegates calls to methods not available on the decorator to the class being decorated. Decorators can be useful when you need to modify the functionality of a class without relying on inheritance. You can use it to add optional features to your objects like logging, access-control, and things of that sort.

A presenter is a type of decorator used to present an object to the view of an application, such as a Blade view, or an API response.

Formatting a User Collection for an API response

Let’s say we have a collection of user objects we need to return in an API response. In Laravel, we basically get this for free by returning the Collection itself, which then gets transformed into a JSON string. Let’s make a call to Eloquent in our controller because that’s how we roll:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    public function index()
    {
        return User::all();
    }
}

The all method will give us a Collection of every User object from the database. Each User will contain every field in the database table. Passwords and other sensitive information will be in the response. Further, Laravel will transform the result of the all method into a json-encoded string by default.

Note: Yeah, yeah I know Eloquent supports hiding attributes in a model’s json representation by adding its key to the $hidden property on the model. Just go along with it.

This wouldn’t be the best thing to provide consumers of our API because of the security problems it might expose. For example, we probably shouldn’t be sending the User’s password hashes in the response. We also may not like the way our created_at and updated_at dates are formatted in the API. Also, if our is_active field is of the tinyint type, we might like that to be a string of true or false in the response. We can get this by wrapping or “decorating” our objects with another object.

Enter the Presenter Pattern

Now that we have a collection of User objects, how do we send them to the view in their decorated form? We’ll need a class to act as our presenter. Our UserPresenter class for this example could look like the following. Notice our presenter will delegate calls to the magic first_name, last_name, and created_at properties to our wrapped model because they’re not available on the presenter:

<?php

namespace App\Users;

class UserPresenter
{
    protected $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function __call($method, $args)
    {
        return call_user_func_array([$this->model, $method], $args);
    }

    public function __get($name)
    {
        return $this->model->{$name};
    }

    public function fullName()
    {
        return trim($this->model->first_name . ' ' . $this->model->last_name);
    }
}

I like dumb analogies, so here’s one for you: a decorator is like putting the Batsuit on Bruce Wayne (the Christian Bale version). And you know, if you grew up with the toys that I did, that Batman has a bunch of different suits available for every situation he may encounter. Like those Batsuits, we can have different decorators for each scenario in which we use the Users in our app. Let’s take this knowledge and rename our presenter into something more content-appropriate. Let’s call it ApiPresenter and place it in a Presenters folder. And while we’re at it, let’s extract the reusable bits to their own abstract Presenter base class:

<?php

namespace App\Presenter;

abstract class Presenter
{
    protected $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function __call($method, $args)
    {
        return call_user_func_array([$this->model, $method], $args);
    }

    public function __get($name)
    {
        return $this->model->{$name};
    }
}

With that cleaned up, we can continue to give the model super powers. Let’s add a new method to the ApiPresenter.

<?php

namespace App\Users\Presenters;

use App\Presenter\Presenter;

class ApiPresenter extends Presenter
{
    public function fullName()
    {
        return trim($this->model->first_name . ' ' . $this->model->last_name);
    }

    public function createdAt()
    {
        return $this->model->created_at->format('n/j/Y');
    }
}

You might be thinking we could leverage Laravel’s mutators to transform the dates the way we desire and avoid this whole presenter business altogether. That would be great if we only needed to have one format for the date, but if we wanted to use the raw created_at date value in other contexts, we could be limiting ourselves by formatting it with mutators.

You may also say, “I could leave the created_at attribute raw and use several mutators to achieve this. You know, like friendlyCreatedAt(), pdfCreatedAt(), and createdAtAsYear()”. Well, you’re a grown adult and you can make your own decisions, but personally, I’m not a fan of this approach. Sure, we’ve all done it before, but one of the great things about throwing this functionality into its own class is that the model doesn’t grow too large, and doesn’t have too many concerns. We’re allowing another class to be concerned with formatting the model for the API.

Let’s add a few more methods to our Presenter.

<?php

namespace App\Users\Presenters;

class ApiPresenter
{
    public function fullName()
    {
        return trim($this->model->full_name . ' ' . $this->model->last_name);
    }

    public function createdAt()
    {
        return $this->model->created_at->format('n/j/Y');
    }

    public function isActive()
    {
        return (bool) $this->model->is_active;
    }

    public function role()
    {
        if ($this->model->is_admin) {
            return 'Admin';
        }

        return 'User';
    }
}

Here we’re casting the model’s is_active attribute to a boolean instead of a tinyint. We’re also providing the API a string representation of the User’s role.

Back to our controller, we can call methods on this presenter to construct the response:

<?php

namespace App\Http\Controllers;

use App\Users\Presenters\ApiPresenter;
use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    public function show($id)
    {
        $user = new ApiPresenter(User::findOrFail($id));

        return response()->json([
            'name' => $user->fullName(),
            'role' => $user->role(),
            'created_at' => $user->createdAt(),
            'is_active' => $user->isActive(),
        ]);
    }
}

This is great! Now we’re returning only the relevant information in the API and have cleaned up the code quite a bit. What’s even better, is if we wanted to use a value not found in the ApiPresenter, but is found on the User model itself, we can simply return it dynamically from the model like we’re used to:

<?php

return response()->json([
    'first_name' => $user->first_name,
    'last_name' => $user->last_name,
    'name' => $user->fullName(),
    'role' => $user->role(),
    'created_at' => $user->createdAt(),
    'is_active' => $user->isActive(),
]);

Decorating Our Entire User Collection

This is a pretty powerful pattern that can be used over and over again to keep your code clean and tidy. But what about the first example where we had an entire Collection of User objects? We could loop over them, and stuff them into a new array:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Users\Presenters\ApiPresenter;

class UsersController extends Controller
{
    public function index()
    {
        $users = User::all();

        $apiUsers = [];

        foreach ($users as $user) {
            $apiUser = new ApiPresenter($user);

            $apiUsers[] = [
                'first_name' => $apiUser->model->first_name,
                'last_name' => $apiUser->model->last_name,
                'name' => $apiUser->fullName(),
                'role' => $apiUser->role(),
                'created_at' => $apiUser->createdAt(),
                'is_active' => $apiUser->isActive(),
            ];
        }

        return response()->json($apiUsers);
    }
}

And this would be fine. We’re all fine. This is fine. You can do it like this and no one will die. However, it does feel a bit icky. Instead, I like to lean on Collection macros in times like these:

<?php

Collection::macro('present', function ($class) {
    return $this->map(function ($model) use ($class) {
        return new $class($model);
    });
});

Note: You can thank Adam Wathan for the complex he gave many developers with his hatred for loops. Also, get his book Refactoring to Collections. Just do it.

Now, if you plop that macro into a service provider we can tag it onto the end of our User::all() call with the presenter we wish to use:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Users\Presenters\ApiPresenter;

class UsersController extends Controller
{
    public function index()
    {
        $users = User::all()
            ->present(ApiPresenter::class)
            ->map(function ($user) {
                return [
                    'first_name' => $user->first_name,
                    'last_name' => $user->last_name,
                    'name' => $user->fullName(),
                    'role' => $user->role(),
                    'created_at' => $user->createdAt(),
                    'is_active' => $user->isActive(),
                ];
            });

        return response()->json($users);
    }
}

To me, this is starting to feel pretty Z. The present macro just becomes another step in your collection pipeline. Every model in the collection gets wrapped with the presenter object. You could then pass this collection onto another decorator to combine several objects into one JSON response if you needed. That would be like putting on multiple Batsuits!

In summary, decorators/presenters are a powerful tool to have at our disposal. They’re easy to write and easy to test. Use them when it makes sense. They’ll help you clean up a lot of code when the time is right.

Unfortunately, it’s not quite there.

If you think the way I do, you might be missing the good ole days when you could just return the collection directly from the controller and get a nice JSON string. We’d have to add support for that. You might also miss being able to cast the collection to an array. We’d have to build that in as well. You know what else would be cool? If we could just call present on the model itself and have that handled. Oh, oh, and what if we had a nice helper function we could use to present any other type of object as well?

Well, let me introduce you to Hemp/Presenter. It does everything we discussed here, plus it integrates those wishlist items I mentioned. And it’s tested. Take it for a spin, and let me know what you think. Enjoy!


Interested in more content like this? Follow me on Twitter to stay updated when I post new articles.


© 2017 David Hemphill