Update, October 2026: I first wrote this in 2016 and updated the code for current PHP and Laravel. If all you need is JSON, Laravel's API Resources now do what the
ApiPresenterbelow does. Presenters are still handy when one model needs different formatting in Blade views, PDFs and CSV exports.
If you've been using Laravel on a project for a while, chances are your Eloquent models have grown. Every new screen, export and endpoint wants the data formatted a little differently, and the easiest place to put that logic is right on the model. Before long you've got a fat model that's hard to work with.
A fat model is still better than prematurely refactoring to a pattern. But it does have some real drawbacks. It's dangerous to let your models drink 'Logic Mountain Dew' on the regular. Not in the sense that it'll bankrupt you, set off every nuke in the US, or broadcast our exact location to a malevolent alien species, but in the sense that it'll be sort of a pain to deal with later on. When a model gets to that point, I like to pull its formatting out into presenters.
Decorators and presenters
A decorator is an object that wraps another object to add functionality to it. Anything the decorator doesn't handle gets passed along to the object it wraps. That lets you add things like logging or access control to a class without reaching for inheritance.
A presenter is a decorator whose job is formatting an object for display, whether that's a Blade view, a PDF, a CSV export or an API response.
The problem
Let's say we need to return a list of users from an API. In Laravel we basically get this for free, because returning a collection from a controller turns it into JSON. Let's make a call to Eloquent in our controller, because that's how we roll:
<?php
namespace App\Http\Controllers;
use App\Models\User;
class UsersController extends Controller
{
public function index()
{
return User::all();
}
}That gives us every column of every user, password hashes included. The dates come out in whatever format the database uses, and if is_active is a tinyint, the API gets a 1 or a 0 instead of true or false.
Note: Yeah, yeah, I know Eloquent can hide attributes from JSON with the
$hiddenproperty on the model. Just go along with it.
We could fix all of that on the model. Or we could wrap each user in another object that knows how the API wants it to look.
A presenter class
Here's a UserPresenter that wraps a model:
<?php
namespace App\Presenters;
use Illuminate\Database\Eloquent\Model;
class UserPresenter
{
public function __construct(protected Model $model)
{
}
public function __call(string $method, array $args): mixed
{
return $this->model->{$method}(...$args);
}
public function __get(string $name): mixed
{
return $this->model->{$name};
}
public function fullName(): string
{
return trim("{$this->model->first_name} {$this->model->last_name}");
}
}The __get and __call methods pass anything the presenter doesn't know about straight through to the model. So $presenter->first_name still works, and so does $presenter->fullName().
I like dumb analogies, so here's one for you. A presenter is like putting the Batsuit on Bruce Wayne (the Christian Bale version). And if you grew up with the toys I did, you know Batman has a different suit for every situation he runs into. We can do the same thing and write a presenter for each place users show up in our app.
So let's rename this one to ApiPresenter, put it in a Presenters folder, and pull the reusable bits into an abstract base class:
<?php
namespace App\Presenters;
use Illuminate\Database\Eloquent\Model;
abstract class Presenter
{
public function __construct(protected Model $model)
{
}
public function __call(string $method, array $args): mixed
{
return $this->model->{$method}(...$args);
}
public function __get(string $name): mixed
{
return $this->model->{$name};
}
}Now the ApiPresenter only has to hold the formatting:
<?php
namespace App\Presenters;
class ApiPresenter extends Presenter
{
public function fullName(): string
{
return trim("{$this->model->first_name} {$this->model->last_name}");
}
public function createdAt(): string
{
return $this->model->created_at->format('n/j/Y');
}
public function isActive(): bool
{
return (bool) $this->model->is_active;
}
public function role(): string
{
return $this->model->is_admin ? 'Admin' : 'User';
}
}isActive turns the tinyint into a real boolean, and role gives the API a readable name instead of an is_admin flag. Neither one touches the model.
You might be thinking we could format the date with an accessor on the model and skip presenters entirely. That works if you only ever need one format. But the moment a PDF wants September 6, 2016 and the API wants 9/6/2016, you're writing friendlyCreatedAt(), pdfCreatedAt() and createdAtAsYear() on the model. You're a grown adult and you can make your own decisions, but I'm not a fan. Putting the formatting in its own class keeps the model small and gives each format a home.
Using it in a controller
Back in the controller, we wrap the user and build the response from the presenter.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Presenters\ApiPresenter;
class UsersController extends Controller
{
public function show(User $user)
{
$user = new ApiPresenter($user);
return response()->json([
'name' => $user->fullName(),
'role' => $user->role(),
'created_at' => $user->createdAt(),
'is_active' => $user->isActive(),
]);
}
}Now the API only returns what it should. And since the presenter passes everything else through to the model, we can still grab regular attributes when we need them:
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(),
]);Presenting a whole collection
That handles one user. But what about the list from the first example? We could loop over every user, wrap it, and stuff the result into an array:
public function index()
{
$apiUsers = [];
foreach (User::all() as $user) {
$apiUser = new ApiPresenter($user);
$apiUsers[] = [
'first_name' => $apiUser->first_name,
'last_name' => $apiUser->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. But it does feel a bit icky, so in times like these I lean on a collection macro:
use Illuminate\Support\Collection;
Collection::macro('present', function (string $class) {
return $this->map(fn ($model) => new $class($model));
});Note: You can thank Adam Wathan for the complex he gave many developers with his hatred of loops. Also, get his book Refactoring to Collections. Just do it.
Plop that macro into the boot method of a service provider, and present becomes one more step in the collection chain:
public function index()
{
$users = User::all()
->present(ApiPresenter::class)
->map(fn (ApiPresenter $user) => [
'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);
}Every user gets wrapped before we map it into the response. If you ever need to, you can hand that collection to another presenter and combine several objects into one response. Multiple Batsuits!
Or use the package
The code above gets you most of the way, but I kept wanting a few more things. I wanted to return presented models straight from a controller and get JSON. I wanted to call present() on the model itself, and I wanted a global present() helper.
So I built all of that into Hemp/Presenter. It does everything in this post plus those wishlist items, and it's tested. Take it for a spin, and let me know what you think. Enjoy!