hemp/presenter
Hemp Presenter is a lightweight Laravel package for wrapping models or data in dedicated presenter classes, giving you a clean place for display formatting, computed fields, and view-ready output while keeping your models and controllers lean.
Install with Composer:
composer require hemp/presenter
No service provider or config file to publish — the package is trait/class-based only.
Add the trait to the model you want to present:
use Hemp\Presenter\Presentable;
class User extends Model
{
use Presentable;
}
Create a presenter that extends the base class:
namespace App\Presenters;
use Hemp\Presenter\Presenter;
class UserPresenter extends Presenter
{
public function fullName(): string
{
return trim("{$this->first_name} {$this->last_name}");
}
public function initials(): string
{
return strtoupper($this->first_name[0] . $this->last_name[0]);
}
}
Present the model:
$user = User::find(1);
$user->present()->fullName; // "David Hemphill"
The first place to look is the presenter class itself: any method defined there is a presentation concern; anything it doesn't define is magically proxied to the underlying model. A good first use case is offloading display formatting (full names, currency, status labels, avatar URLs, date formatting) from Blade templates or API resources into a dedicated, testable class.
If the convention-based presenter resolution doesn't match your namespace layout, declare it explicitly on the model:
class User extends Model
{
use Presentable;
protected $presenter = UserPresenter::class;
}
Blade formatting
<h1>{{ $user->present()->fullName }}</h1>
<p>Joined {{ $user->present()->joinedOn }}</p>
Replace inline ->format() chains and conditional string-building in views with named presenter methods.
Context-specific presenters. Pass a presenter in explicitly when the same model needs different rendering in different contexts (admin panel vs. public profile vs. API):
$user->present(AdminUserPresenter::class);
$user->present(PublicUserPresenter::class);
$user->present(new InvoiceUserPresenter($user, $currencyService));
This is the cleanest way to inject dependencies the base class doesn't provide.
Status and badge helpers. Put enum/constant-to-display mapping in the presenter so templates stay dumb:
public function statusLabel(): string
{
return match ($this->status) {
'a' => 'Active',
's' => 'Suspended',
default => 'Unknown',
};
}
public function statusClass(): string
{
return $this->status === 'a' ? 'badge-success' : 'badge-danger';
}
Chaining into model methods. Because the presenter forwards unknown calls, you can mix presenter helpers and model methods:
$user->present()->subscription()->renewsAt;
Prefer calling $this->model->... explicitly when the intent matters for readability.
API responses. For resource controllers, present the model and then flatten it, or expose the presenter through an API Resource:
return response()->json([
'name' => $user->present()->fullName,
'email' => $user->email,
'member_since' => $user->present()->joinedOn,
]);
Don't try to make the presenter a full replacement for Eloquent serialization — treat it as a projection layer.
Testing. Presenters are plain classes, so test them directly:
$presenter = new UserPresenter(new User(['first_name' => 'Ada', 'last_name' => 'Lovelace']));
$this->assertSame('Ada Lovelace', $presenter->fullName);
No database needed, which is the main long-term benefit over inline accessors.
Keep presenters read-only. They should never persist, mutate the model, or perform queries directly — treat any I/O as a sign the logic belongs in a service or resource instead.
$this->firstname (typo) resolves to null rather than throwing. If presenters are critical, write tests for each method or wrap risky access with $this->model->getAttribute().$presenter only blows up when present() is called. Use explicit $presenter properties in any model where the default convention is ambiguous.present() builds a new instance each call. Avoid calling it repeatedly inside a loop; assign once:
$p = $user->present();
$p->fullName; $p->initials;
$this->relation->…, ensure the relation is loaded first or you'll reintroduce N+1 queries. A common bug is $user->present()->latestOrderTotal on a paginated list.__get delegates to the model, so casts still work, but a null relation or attribute will happily return null into your string-concatenation. Guard in the presenter rather than in the Blade template.isset() semantics can surprise you when the model defines custom __isset or attributes are computed lazily — test null checks explicitly.toArray()/jsonSerialize() on a presenter unless you fully intend to shadow the model's serialization; for API endpoints, prefer Laravel's API Resources or an explicit array shape.save, update) can hide surprising behavior — name presenter methods as verbs/nouns that clearly describe presentation (displayName, statusLabel).new MyPresenter($user, $service).__get/__call in your own base presenter if you need caching, fallback chains, or logging around attribute access — but keep it thin; the package intentionally has no config surface.Attribute casts, and API Resources, which cover the same ground with framework guarantees. The presenter approach shines when formatting logic is large enough to warrant its own tested class and a reusable interface across multiple models.null unexpectedly, add dd($presenter->model) to confirm which model instance was decorated — presenters are commonly mis-bound by passing the wrong class in present().How can I help you explore Laravel packages today?