Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Presenter Laravel Package

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...

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

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.

Gotchas and Tips

  • It is effectively abandoned. Laravel 4 only, no Laravel 5+ support, 16 stars, zero dependents. Treat it as a pattern to copy, not a package to depend on. Vendoring the two classes removes the constraint problem entirely and costs nothing.
  • Property collisions fatal at class-load. If both the trait and your model declare the same property with different visibility or default, PHP throws a fatal error. Declare $presenter (and $presenterInstance if you override) in exactly one place — the model.
  • Missing $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.
  • Typos fall through to the entity. The base class proxies unknown calls to the model, so $user->present()->fullNmae() either errors obscurely or silently calls a same-named model method. Keep presenter methods short and covered by unit tests.
  • Memoization is per model instance. 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.
  • Don't put presenters in $appends. They aren't accessors and won't serialize. json_encode($user) ignores them by design — build the array explicitly.
  • Blade escaping differs by era. On Laravel 4, {{ }} 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).
  • Keep them pure. No writes, no authorization checks, no queries you didn't eager load. A presenter that touches the database inside a loop is the most common way this pattern degrades a page from 8 queries to 400.
  • Extension point worth knowing: overriding 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.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource