lewis/presenter
lewis/presenter is a lightweight Laravel package for wrapping models in dedicated presenter classes, exposing clean, reusable, view-ready data while keeping formatting and presentation logic out of Blade templates, controllers, and Eloquent models. It provides a simple, testable layer for consistent...
lewis/presenter is a two-class package: a PresentableTrait you drop on an Eloquent model, and a Presenter base class that wraps the model so view-formatting logic lives outside both the model and the Blade template. The whole library is small enough to read end-to-end in five minutes — start with src/Lewis/Presenter/Presenter.php and PresentableTrait.php before anything else.
Install. The package targets Laravel 4 (illuminate/support ~4.0), so composer require lewis/presenter will be rejected by any modern Laravel app. On an L4 codebase, add it normally:
"require": { "lewis/presenter": "dev-master" }
On anything newer, don't fight the constraint — copy the two classes into app/Support/Presenter/ (or your own namespace) and register them via PSR-4 in composer.json. You are vendoring ~50 lines, not adopting a dependency.
First use case: a full name. Add the trait and point it at a presenter class:
// app/models/User.php
use Lewis\Presenter\PresentableTrait;
class User extends Eloquent
{
use PresentableTrait;
protected $presenter = 'UserPresenter';
}
// app/presenters/UserPresenter.php
use Lewis\Presenter\Presenter;
class UserPresenter extends Presenter
{
public function fullName()
{
return trim($this->first_name.' '.$this->last_name);
}
}
Then in the view:
<h1>{{ $user->present()->fullName() }}</h1>
present() lazily instantiates and memoizes the presenter for that model instance; the base class proxies property reads to the underlying entity, so $this->first_name reaches the model attribute. Use $this->entity when you want to be explicit or pass the model itself somewhere.
That's the entire mental model. Everything below is how you scale it past one method.
Formatting helpers on the presenter, never in Blade. The sweet spot is anything you'd otherwise write as {{ $user->first_name . ' ' . $user->last_name }} or a ternary chain:
class UserPresenter extends Presenter
{
public function fullName() { /* ... */ }
public function statusBadge()
{
$class = $this->active ? 'label-success' : 'label-default';
return '<span class="label '.$class.'">'.e($this->statusLabel()).'</span>';
}
public function joinedOn()
{
return $this->created_at->format('j M Y');
}
}
Shared base presenter. Create App\Presenters\Presenter extending the package class with cross-cutting helpers (money(), excerpt(), ago(), initials()), then have UserPresenter, PostPresenter, etc. extend that. This is the cheapest way to get consistency without a second dependency.
Relationships — eager load or pay for it. Presenters make lazy loading easy to hide. This is fine:
public function authorName()
{
return $this->entity->author->present()->fullName();
}
…but in @foreach ($posts as $post) it is one query per row. Always Post::with('author')->get() in the controller; the presenter should never be the place where you discover N+1s.
Presenters returning markup vs. data. Prefer returning plain strings and keeping HTML in partials. When you do return markup (badges, links), escape user-supplied fragments inside the presenter with e() — the presenter is where XSS gets introduced because it's the layer that says "this is safe HTML."
Container injection. The trait constructs presenters with new $this->presenter($this), so no DI. If a presenter needs a service, override present() in the model — class methods win over trait methods in PHP:
public function present()
{
if (! $this->presenterInstance) {
$this->presenterInstance = App::make($this->presenter, [$this]);
}
return $this->presenterInstance;
}
Testing. Presenters are plain classes with a single constructor argument, so unit-test them with an unsaved model or a stub object — no database, no HTTP:
$user = new User(['first_name' => 'Ada', 'last_name' => 'Lovelace']);
$this->assertEquals('Ada Lovelace', (new UserPresenter($user))->fullName());
This is the strongest argument for the package: it turns view-conditional soup into testable methods.
As a lightweight transformer. For small JSON endpoints, present()->toArray()-style methods can replace a dedicated fractal/transformer layer. Once you need includes, sparse fieldsets, or nested collections, move to a real transformer — presenters don't scale to that.
$presenter (and $presenterInstance if you override) in exactly one place — the model.$presenter fails at render time, not boot time. The exception surfaces the first time a view calls present(), which may be deep in a page. Add a test that asserts $model->present() for each presentable model, or you'll ship a 500 to production.$user->present()->fullNmae() either errors obscurely or silently calls a same-named model method. Keep presenter methods short and covered by unit tests.present() caches the presenter on the object; a fresh model gets a fresh presenter. If a presenter caches a derived value in a property and the model changes afterwards, you get stale output. Compute on demand instead.$appends. They aren't accessors and won't serialize. json_encode($user) ignores them by design — build the array explicitly.{{ }} output was raw, so presenter HTML "just worked" and XSS was your responsibility. On modern Blade you need {!! !!}, and the same code becomes an injection risk. Decide per method and name HTML-returning methods obviously (statusBadge, linkTo).present() (for DI) and overriding __get/__call on your base presenter (for fallbacks, logging, or attribute casting) are the only two hooks you get. Everything else is plain PHP.How can I help you explore Laravel packages today?