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

guilhermegonzaga/presenter

A Laravel presenter package that wraps models and other objects in dedicated presenter classes, keeping views and controllers clean. Define reusable presentation methods for formatting, computed values, and view-ready output while preserving access to the underlying object.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer; the service provider is auto-discovered on Laravel 5.5+.

composer require guilhermegonzaga/presenter

Where to look first: open vendor/guilhermegonzaga/presenter/src and read the PresentableTrait and the abstract Presenter base class. The trait is the contract you attach to models (it exposes the present() entry point), and the base presenter defines how property access is proxied back to the underlying model. Those two files tell you the exact method names and magic-access behaviour for the version you installed — confirm them before copying snippets from blog posts, since the API surface is small and naming has shifted between tags.

Minimal wiring:

use GuilhermeGonzaga\Presenter\PresentableTrait;

class User extends Model
{
    use PresentableTrait;

    protected $presenter = \App\Presenters\UserPresenter::class;
}
use GuilhermeGonzaga\Presenter\Presenter;

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

    public function price(): string
    {
        return 'R$ ' . number_format($this->model->price / 100, 2, ',', '.');
    }
}

First use case: stop formatting in Blade. Replace {{ $user->first_name . ' ' . $user->last_name }} with {{ $user->present()->fullName() }}, and move every number_format, Carbon::parse(...)->format(...), and status-to-label match out of your views and into the presenter. That single refactor is the fastest way to see whether the pattern fits your codebase.

Implementation Patterns

Blade-first formatting. The presenter is a view-model: it is the only place that knows how a value should be displayed. Keep raw values on the Eloquent model and presentation strings on the presenter, so the same model can back an API (raw) and a web page (presented) without conditional logic leaking into either.

One presenter per concern, not per model. Rather than a 600-line UserPresenter, compose small presenters (UserProfilePresenter, UserBillingPresenter) and pick them at the call site or via a factory. The package resolves the presenter class from the model, so if you need runtime selection, instantiate presenters directly with the model instead of relying on the $presenter property.

Accessing relationships. Inside a presenter, $this->relation proxies to the model's loaded relation, which lets you write $this->address->city in a getter. This is convenient but dangerous — see the N+1 note below. Prefer explicit $this->model->relation when you want the intent to be obvious to reviewers.

Derived/conditional labels. Status badges are the canonical case:

public function statusLabel(): string
{
    return match ($this->model->status) {
        'a' => 'Active',
        's' => 'Suspended',
        default => 'Unknown',
    };
}

public function statusColor(): string
{
    return $this->model->status === 'a' ? 'green' : 'red';
}

Caching expensive derivations. Presenters are typically instantiated fresh per call, so a getter that hits the database or does heavy formatting runs every time it is touched. If a value is reused across a view, compute it once and stash it on a property:

private ?string $cachedTotal = null;

public function total(): string
{
    return $this->cachedTotal ??= number_format($this->model->total_cents / 100, 2);
}

API + Blade coexistence. Do not add presenters to $appends; that couples serialization to display formatting. Instead, in API Resources, call the presenter explicitly for fields that genuinely belong in the payload, and keep the rest raw.

Testing. Presenters are plain classes — unit-test them without a database by constructing them with a model instance populated in memory. Assert on output strings, not on the magic __get proxy, so a future package upgrade that changes proxying doesn't silently break your suite.

Gotchas and Tips

Every present() call builds a new instance. Don't assume memoization. If your Blade partial touches the presenter five times, either assign it once in the controller ($user = $user->present()) or cache derived values on the presenter. This is the most common source of "why is this slow" reports.

__get hides missing attributes. Because property access is proxied through the model, a typo like $this->ful_name may return null instead of throwing, and the bug surfaces as a blank cell in production. Prefer $this->model->attribute where you control the name, or add explicit return types (: string) so PHP fails loudly on null.

N+1 in presenter getters. A presenter that touches $this->address->city inside a @foreach over 500 users is an invisible N+1 — the query is one level removed from the loop. Eager-load in the controller (User::with('address')) and treat relationship access in presenters as a code smell to review.

Never query from a presenter. Presenters should be pure formatting. The moment a getter calls Model::where(...), the class stops being unit-testable and starts being a hidden repository. Inject or preload what you need.

Config/property quirks. The presenter class is usually read from a $presenter property on the model; some versions accept a presentable() method override instead. If present() throws "presenter not defined," check the property name and visibility against the installed tag — a protected vs public mismatch is a frequent cause.

Serialization is not automatic. Presenters do not change toArray()/toJson() output. Anything you want in a JSON response must be added explicitly. This is a feature (raw data for APIs, formatted for views) but surprises people migrating from accessor-heavy models.

Extension points. The base presenter is deliberately thin: override the constructor if you need dependency injection, add __call passthrough only as a last resort (it obscures stack traces), and wrap shared formatting helpers (money(), mask(), date()) in a trait that multiple presenters use. Keep the base class untouched so package upgrades stay painless.

Debugging tip. When output looks wrong, dd($user->present()) shows the presenter wrapper, not the data — dump $user->present()->model (or your version's accessor) to see the underlying attributes. That distinction resolves most "the presenter is empty" confusion in seconds.

Version pinning. With a small, low-traffic package (46 stars), treat the tag as an API contract: pin a minor version in composer.json and read the diff before upgrading, since there is no large community to surface breaking changes for you.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core