culturegr / presenter
Eloquent Model Presenter for Laravel applications
Installs: 1 315
Dependents: 0
Suggesters: 0
Security: 0
Stars: 14
Watchers: 4
Forks: 1
Open Issues: 0
Requires
- ext-json: *
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- laravel/legacy-factories: ^v1.4
- mockery/mockery: ^1.6.1
- orchestra/testbench: ^8.25
- phpunit/phpunit: ^10.0
README
This package provides an easy way to create Presenters that can be used to decorate an Eloquent Model. It is heavily inspired by Hemp Presenter and Laravel API Resources, however it comes with some key differences:
- It provides an easy way to handle paginated models
- It does not focus solely in JSON API responses but it serves as a general purpose decorator that can be used anywhere in the project
TL;DR
A Decorator is an object that wraps another object in order to add functionality to the wrapped object. It also delegates calls to methods not available on the decorator to the class being decorated. Decorators can be useful when you need to modify the functionality of a class without relying on inheritance. You can use it to add optional features to your objects like logging, access-control, and things of that sort. A Presenter is a type of decorator used to present an object to the view of an application, such as a Blade view, or an API response.
Installation
Via Composer:
$ composer require culturegr/presenter
In Laravel 5.5+, the package's service provider should be auto-discovered, so you won't need to register it. If for some reason you need to register it manually you can do so by adding it to the providers array in config/app.php
:
'providers' => [ // ... CultureGr\Presenter\PresenterServiceProvider::class, ],
Usage
Creating Presenter Classes
A Presenter class can be generated by running the make:presenter
Artisan command:
php artisan make:presenter UserPresenter
This will generate an empty Presenter
class inside of app/Presenters
Customizing Presenter Classes
Presenters are simple classes designed to encapsulate complex or repetitive view logic. For example, here is a simple UserPresenter
class:
<?php namespace App\Presenters; use CultureGr\Presenter\Presenter; class UserPresenter extends Presenter { public function fullname() { return $this->firstname.' '.$this->lastname; } }
Note that the original model's properties (firstname
, lastname
) can be directly accessed using the $this
variable. This is because the Presenter
class automatically proxies property and method access down to the underlying model.
This class has a custom method fullname
that can be called wherever an instance of this Presenter is used.
$presentedUser->firstname; //=> 'John' $presentedUser->lastname; //=> 'Doe' $presentedUser->fullname() //=> 'John Doe'
The Presenter class requires you to implement an abstract toArray
method which returns the array of attributes that should be used when the presenter is converted to an array or JSON. This ensures consistent behavior across all presenter classes.
<?php namespace App\Presenters; use CultureGr\Presenter\Presenter; class UserPresenter extends Presenter { public function fullname() { return $this->firstname.' '.$this->lastname; } public function toArray() { return [ 'id' => $this->id, 'fullname' => $this->fullname() // ... ]; } }
Presenting Single Models
Once the presenter is defined, it may be used to create a presented model using the make
factory method:
$user = User::first(); $presentedUser = UserPresenter::make($user);
The presented model may be used anywhere in the project and can be passed to views, returned from controllers, or returned from API routes. Presenters implement Arrayable
, Jsonable
, JsonSerializable
, and ArrayAccess
interfaces, which means they can be:
- Automatically converted to arrays when needed (no need to call
toArray()
explicitly) - Automatically converted to JSON when returned from routes (no need to call
toJson()
explicitly) - Accessed like arrays using square bracket notation (e.g.,
$presenter['key']
)
<?php namespace App\Http\Controllers; use App\Models\User; use App\Presenters\UserPresenter; class UserController extends Controller { public function show($id) { // No need to call toArray() - automatic conversion happens when needed return view('users.show', [ 'user' => UserPresenter::make(User::find($id)) ]); } }
Presenting Collections
To present a collection of models, the static collection
method on the Presenter class can be used:
$users = User::all(); $presentedUsers = UserPresenter::collection($users);
The collection of presented models may be used anywhere in the project. Thanks to the automatic conversion features, the collection can be directly returned from a route or controller without explicitly calling toArray()
:
<?php namespace App\Http\Controllers; use App\Models\User; use App\Presenters\UserPresenter; class UserController extends Controller { public function index() { $users = User::all(); return UserPresenter::collection($users); } }
The output will be something like this:
[ {"id": "1", "fullname": "John Doe"}, {"id": "2", "fullname": "Jane Doe"} ]
Presenting Paginated Collections
To present a paginated collection of models with pagination metadata, the static pagination
method on the Presenter class can be used:
$users = User::paginate(); $presentedUsers = UserPresenter::pagination($users);
The paginated collection of presented models may be used anywhere in the project and can be directly returned from a route or controller:
<?php namespace App\Http\Controllers; use App\Models\User; use App\Presenters\UserPresenter; class UserController extends Controller { public function index() { $users = User::paginate(); return UserPresenter::pagination($users); } }
The output will contain the presented models wrapped in a data
key, along with links
and meta
keys containing pagination information:
{ "data": [ {"id": "1", "fullname": "John Doe"}, {"id": "2", "fullname": "Jane Doe"} ], "links":{ "first": "http://example.com/pagination?page=1", "last": "http://example.com/pagination?page=1", "prev": null, "next": null }, "meta":{ "current_page": 1, "from": 1, "last_page": 1, "path": "http://example.com/pagination", "per_page": 15, "to": 10, "total": 10 } }
Using the Presentable Trait
The package provides a Presentable
trait that can be used to present models directly from the model instance. This allows for a more elegant syntax when presenting models, collections, and paginators.
To use the trait, first add it to your model:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use CultureGr\Presenter\Presentable; class User extends Model { use Presentable; // ... }
Then you can present the model directly using the present
method:
$user = User::first(); $presentedUser = $user->present(UserPresenter::class);
This allows for a more fluent syntax when presenting models:
$presentedUser = User::first()->present(UserPresenter::class);
For collections, you can use the presentCollection
method directly on the collection:
$users = User::all(); $presentedUsers = $users->presentCollection(UserPresenter::class);
This is equivalent to using the static collection
method on the Presenter class, but provides a more fluent API.
You can also use the presentCollection
method directly on a paginator instance:
$users = User::paginate(); $presentedUsers = $users->presentCollection(UserPresenter::class);
When used on a paginator, the presentCollection
method automatically detects that it's being called on a paginator and returns a structured result with the presented models wrapped in a data
key, along with links
and meta
keys containing pagination information.
Automatic Array and JSON Conversion
Presenters now implement the necessary interfaces to be automatically converted to arrays or JSON when needed:
Arrayable
: Allows automatic conversion to arraysJsonable
: Allows automatic conversion to JSON stringsJsonSerializable
: Ensures proper JSON serializationArrayAccess
: Allows accessing presenters like arrays using square bracket notation
This means you no longer need to explicitly call toArray()
or toJson()
in most contexts:
// Before $array = $presenter->toArray(); $json = $presenter->toJson(); // Now - automatic conversion happens when needed $array = $presenter; // Automatic conversion to array $json = json_encode($presenter); // Automatic conversion to JSON return $presenter; // Automatic conversion when returned from routes
Array Access Support
Presenters can now be accessed like arrays:
$presenter = UserPresenter::make($user); $id = $presenter['id']; // Same as $presenter->id
Abstract toArray Method
The toArray()
method is now abstract, which means all presenter classes must implement it. This ensures consistent behavior across all presenters.
Note for Existing Users
If you're currently calling toArray()
explicitly when using Presenters (e.g., UserPresenter::make($user)->toArray()
), please note that these calls will continue to work as before. However, they are no longer necessary in most contexts due to the new automatic conversion feature. You can safely remove these explicit calls if desired, which will make your code cleaner and more concise.
Testing
$ composer test
License
Please see the license file for more information.
Credits
- Huge shout out to David Hemphill
- Awesome Laravel/PHP community