Installation:
composer require laracasts/presenter
No additional configuration is required—just use it.
First Use Case:
Create a presenter for a model (e.g., User). Place it in app/Presenters/UserPresenter.php:
namespace App\Presenters;
use App\Models\User;
use Laracasts\Presenter\Presenter;
class UserPresenter extends Presenter
{
public function fullName()
{
return "{$this->first_name} {$this->last_name}";
}
public function avatarUrl()
{
return $this->avatar ? "/uploads/{$this->avatar}" : '/default-avatar.png';
}
}
Enable Presenters:
In your AppServiceProvider (or a model's boot() method), register the presenter:
use App\Models\User;
use App\Presenters\UserPresenter;
public function boot()
{
User::addPresenter(UserPresenter::class);
}
Usage in Blade:
{{ $user->fullName() }} <!-- Outputs: "John Doe" -->
{{ $user->avatarUrl() }} <!-- Outputs: "/uploads/john-doe.jpg" -->
Model-Specific Presenters:
PostPresenter, ProductPresenter).class PostPresenter extends Presenter
{
public function formattedDate()
{
return $this->created_at->format('M d, Y');
}
public function excerpt()
{
return Str::limit($this->body, 200);
}
}
Collection Presenters:
$posts = Post::all(); // Each Post now has presenter methods
foreach ($posts as $post) {
echo $post->formattedDate(); // Works on collection items
}
Dynamic Presenters:
class MediaPresenter extends Presenter
{
public function url()
{
return match ($this->type) {
'image' => "/images/{$this->path}",
'video' => "/videos/{$this->path}",
default => "#",
};
}
}
Presenter Inheritance:
class BaseUserPresenter extends Presenter
{
public function gravatar()
{
return "https://www.gravatar.com/avatar/{$this->email}?d=mp";
}
}
class AdminUserPresenter extends BaseUserPresenter
{
public function roleBadge()
{
return '<span class="badge">Admin</span>';
}
}
Presenter in Controllers:
public function show(User $user)
{
return view('user.profile', [
'user' => $user, // $user->fullName() works in Blade
]);
}
Presenter in API Responses:
class ApiUserPresenter extends Presenter
{
public function toArray()
{
return [
'id' => $this->id,
'name' => $this->fullName(),
'avatar' => $this->avatarUrl(),
];
}
}
Blade Directives: Create a custom Blade directive for reusable presenter logic:
Blade::directive('present', function ($expression) {
return "<?php echo {$expression}->{$expression}(); ?>";
});
Usage:
@present($user, 'fullName')
Presenter for Eloquent Relationships: Extend presenters to handle relationships:
class CommentPresenter extends Presenter
{
public function authorName()
{
return $this->user->fullName(); // Calls UserPresenter
}
}
Presenter Caching: Cache presenter output for performance-critical views:
public function fullName()
{
return Cache::remember("user_{$this->id}_name", now()->addHours(1), function () {
return "{$this->first_name} {$this->last_name}";
});
}
Presenter for Non-Model Data: Use presenters with arrays or DTOs:
$data = ['name' => 'John', 'age' => 30];
$presenter = new class($data) extends Presenter {
public function greeting()
{
return "Hi, I'm {$this->name} and I'm {$this->age} years old.";
}
};
Presenter Not Registered:
Call to undefined method App\Models\User::fullName().addPresenter() in a model's boot() method or AppServiceProvider.Presenter Overwriting Model Methods:
toArray()).presenter_ or use a naming convention like format*():
public function formatDate() { ... }
Circular Dependencies:
UserPresenter calls PostPresenter, which calls UserPresenter).if ($this->post) { ... }).Presenter in Livewire/Alpine:
@once directives in Blade or compute properties in Alpine:
<div x-data="{ name: @js($user->fullName()) }">...</div>
Presenter Serialization:
toArray() or toJson():
public function toArray()
{
return array_merge(parent::toArray(), [
'presenter_data' => [
'full_name' => $this->fullName(),
],
]);
}
Check Registered Presenters: Use Tinker to verify presenters are loaded:
php artisan tinker
>>> $user = App\Models\User::first();
>>> method_exists($user, 'fullName')
Presenter Method Introspection: Dump presenter methods for a model:
dd(get_class_methods($user)); // Look for presenter methods
Presenter Isolation: Test presenters in isolation:
$user = new \App\Models\User();
$user->first_name = 'John';
$user->last_name = 'Doe';
$presenter = new \App\Presenters\UserPresenter($user);
dd($presenter->fullName()); // Debug without DB
Custom Presenter Events: Trigger events when presenters are called:
class UserPresenter extends Presenter
{
public function fullName()
{
event(new PresenterCalled($this, 'fullName'));
return "{$this->first_name} {$this->last_name}";
}
}
Presenter Middleware: Add middleware to presenters (e.g., logging, caching):
class UserPresenter extends Presenter
{
public function __call($method, $args)
{
if (str_starts_with($method, 'presenter_')) {
Log::info("Presenter method called: {$method}");
return $this->{$method}(...$args);
}
}
}
Dynamic Presenter Resolution: Resolve presenters dynamically based on context:
User::addPresenter(function ($model, $context) {
return match ($context) {
'api' => new ApiUserPresenter($model),
default => new UserPresenter($model),
};
});
Presenter for API Resources: Integrate with Laravel's API Resources:
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'name' => $this->resource->fullName(),
'avatar' => $this->resource->avatarUrl(),
];
}
}
Presenter Testing: Mock presenters in tests:
$user = new User();
$presenter = Mockery::mock(User
How can I help you explore Laravel packages today?