Getting Started
- Install with
composer require yaro/presenter, then confirm what you got: composer show yaro/presenter. The last tagged release is from 2021-02-22, so test it on a branch against your Laravel/PHP versions before adopting it broadly.
- Look first at the README and
vendor/yaro/presenter/src/. Identify:
- the service provider and whether it is auto-discovered,
- the base presenter class,
- the trait/helper used to attach a presenter to a model,
composer.json constraints for illuminate/* and PHP.
- Create one presenter for your first model, e.g.
app/Presenters/UserPresenter.php. If the package supplies a base class, extend it; otherwise a small wrapper class around the model is enough to prove the pattern:
namespace App\Presenters;
use App\Models\User;
class UserPresenter
{
public function __construct(private User $user) {}
public function fullName(): string
{
return trim("{$this->user->first_name} {$this->user->last_name}");
}
public function createdAt(): string
{
return $this->user->created_at->format('M j, Y');
}
}
- Attach it using the package’s documented trait/property pattern if available. If the package has no trait, instantiate it directly in the controller or view:
$presenter = new UserPresenter($user);
- First real use case: move formatting out of Blade. Replace this:
{{ $user->first_name }} {{ $user->last_name }} — {{ $user->created_at->format('M j, Y') }}
with:
{{ $user->present()->fullName }} — {{ $user->present()->createdAt }}
or, if wiring manually:
{{ (new \App\Presenters\UserPresenter($user))->fullName() }}
Implementation Patterns
- Use one presenter per model or domain concept. Keep it focused on display concerns: names, dates, money, status labels, URLs, badges, and preformatted HTML fragments.
- Keep presenters at the edges of the application: controllers, Blade views, API resources, mailables, and admin panels. Do not move business rules or persistence into presenters.
- Prefer explicit presenter calls in controllers when the same model is used in multiple contexts:
public function show(User $user)
{
return view('users.show', [
'user' => $user,
'presenter' => new UserPresenter($user),
]);
}
- For Blade-heavy apps, the trait-based pattern is usually the least noisy:
{{ $user->present()->statusLabel() }}
- For API resources, call the presenter inside the resource rather than exposing presenter methods on the model:
public function toArray($request): array
{
$presenter = new UserPresenter($this->resource);
return [
'id' => $this->id,
'name' => $presenter->fullName(),
'created_at' => $presenter->createdAt(),
];
}
- For collections, map consistently. If the package supports
$users->map->present(), verify it; otherwise use an explicit map:
$users->map(fn (User $user) => new UserPresenter($user));
- Inject services into presenters when formatting needs them, e.g. a money formatter, URL generator, or locale resolver. Resolve presenters through the container if they have dependencies.
- Test presenters as plain units with model factories or unsaved models. You usually do not need a database:
$user = new User(['first_name' => 'Ada', 'last_name' => 'Lovelace']);
$this->assertSame('Ada Lovelace', (new UserPresenter($user))->fullName());
- Use presenters in mailables and notifications too. They are a good place to centralize “friendly” output such as
$order->present()->total() or $invoice->present()->dueDate().
- If the package provides config, publish it only after reading the defaults. Avoid changing global presenter behavior unless you have a clear cross-cutting need.
Gotchas and Tips
- Adoption risk is the main gotcha: 2 stars, 0 dependents, and a 2021 release mean you should read the source, pin the version, and be prepared to fork. If the package is only a thin trait/base class, copying the pattern into your app may be safer than depending on it.
- Check PHP and Laravel compatibility carefully. A 2021 package may not support PHP 8.1+ or newer Laravel without deprecation warnings or method-signature issues.
- Model serialization will not automatically include presenter methods.
toArray()/JSON responses only include attributes and appended accessors. Use API resources or explicit presenter calls instead of assuming $user->present()->fullName() appears in JSON.
- Avoid queries inside presenters. A presenter that lazy-loads relations or counts rows can create N+1 queries when used in loops. Pass loaded data in or keep presenters purely formatting-based.
- Presenters should usually be stateless per model instance. Avoid registering a presenter as a singleton or storing request-specific state on a shared presenter.
- If a presenter returns HTML, treat it as trusted output only after sanitization. Blade’s
{{ }} will escape it; {!! !!} will not. Do not let user input flow into presenter-generated HTML unescaped.
- Be explicit about timezone and locale. Date formatting in presenters should use the app’s configured timezone and the user’s locale where relevant; otherwise “created at” output will be inconsistent across views, mail, and APIs.
- Watch for method collisions. If your model already has a
present() method or the trait conflicts with another trait, alias the trait method or use manual presenter instantiation.
- If something “does not work,” debug in this order: confirm the service provider is registered/auto-discovered, confirm the presenter class namespace is correct, run
composer dump-autoload, clear config cache with php artisan config:clear, then dd($user->present()) or dd(new UserPresenter($user)).
- Extension points to look for: overriding the base presenter’s model accessor, binding a presenter interface in the container, custom formatting helpers, and package config. If those do not exist, keep your presenters as small application classes rather than fighting the package.
- Because the package is lightly adopted, add a smoke test that instantiates a presenter and asserts one formatted value. That test will catch most upgrade breakage quickly.