deefour / presenter
Presenters/Decorators for PHP Objects
Installs: 2 445
Dependents: 0
Suggesters: 0
Security: 0
Stars: 12
Watchers: 2
Forks: 1
Open Issues: 0
Requires
- php: >=5.5.0
- illuminate/support: ^5.0
Requires (Dev)
- friendsofphp/php-cs-fixer: 2.1.0
- illuminate/container: ^5.0
- illuminate/support: ^5.0
- phpspec/phpspec: ^2.4.0
README
Object-oriented presentation logic.
Getting Started
Run the following to add Presenter to your project's composer.json
. See Packagist for specific versions.
composer require deefour/presenter
>=PHP5.5.0
is required.
The Resolver
The Deefour\Presenter\Resolver
determines the FQN of a presenter class associated with an object. The default behavior of the resolver is to append 'Presenter'
to the Article
FQN.
use Deefour\Presenter\Resolver; (new Resolver)->presenter(new Article); //=> 'ArticlePresenter'
This behavior can be customized by passing a callable to the resolver.
use Deefour\Presenter\Resolver; $resolver = new Resolver; $resolver->resolveWith(function ($instance) { return "App\Presenters\" . get_class($instance) . 'Presenter'; }); $resolver->presenter(new Article); //=> 'App\Presenters\ArticlePresenter'
The resolver will look for a modelClass()
method on an object. If found, the returned FQN will be used instead of the object itself.
class BlogPost { static public function modelClass() { return Article::class; } } (new Resolver)->presenter(new BlogPost); //=> 'ArticlePresenter'
Instantiation
Instantiating an instance of a presenter is your responsibility.
use Deefour\Presenter\Resolver; $article = new Article; $presenterName = (new Resolver)->presenter($article); $presenter = new $presenterName($article);
use Deefour\Presenter\Resolver; (new Resolver)->presenter(new Article); //=> 'BlogPresenter'
If the resulting FQN from the resolver does not match an existing, valid class name, null
will be returned or a NotDefinedException
will be thrown.
use Deefour\Presenter\Resolver; (new Resolver)->presenter(new ObjectWithoutPresenter); //=> null (new Resolver)->presenterOrFail(new ObjectWithoutPresenter); //=> throws NotDefinedException
Presenters
The presenters themselves extend Deefour\Presenter\Presenter
.
use Deefour\Presenter\Presenter; class ArticlePresenter extends Presenter { public function isDraft() { return $this->_model->isDraft() ? 'Yes' : 'No'; } }
The API
A quick overview of the API available.
use Deefour\Producer\Factory; $presenter = (new Factory)->make(new Article, 'presenter'); //=> ArticlePolicy $presenter->_model; //=> Article $presenter->_model->isDraft(); //=> false $presenter->isDraft(); //=> 'No' $presenter->is_draft; //=> 'No' $presenter->_model()->published; //=> true $presenter->published; //=> true
A few things to notice:
- The underlying object decorated by the presenter can be accessed via the
$_model
property ormodel()
method. - Any property or method publicly accessible on the underlying object can also be accessed directly through the presenter.
- Any publicly accessible, camel-cased method on the presenter or underlying model can be accessed via snake-cased property access.
Automatic Presenter Resolution
When a property or method is resolved through the __get()
or __call()
methods on the presenter, an attempt will be made to resolve and wrap the return value in a presenter too.
namespace App; use Illuminate\Support\Collection; class Article { public function category() { return new Category; } public function tags() { $collection = new Collection; $collection->push(new Tag); $collection->push(new Tag); $collection->push(new Tag); return $collection; } }
Given the existence of ArticlePresenter
, CategoryPresenter
, and TagPresenter
, the following will be returned
use Deefour\Presenter\Resolver; $presenter = (new Resolver)->presenter(new Article); //=> ArticlePresenter (new $presenter)->category; //=> CategoryPresenter (new $presenter)->tags->first(); //=> TagPresenter
Note: The collection resolution works by looking for an instance of
IteratorAggregate
. The iterator is used to loop through the collection and generate presenters for each item. An attempt is then made to instantiate a new instance of the original object implementingIteratorAggregate
. That is the return value.
If you want access to the raw association, simply request it from the underlying object.
$presenter->_model->tags()->first(); //=> Tag
Contribute
- Issue Tracker: https://github.com/deefour/presenter/issues
- Source Code: https://github.com/deefour/presenter
Changelog
3.0.1 - November 7, 2017
- Check for the existence of a method on the underlying modely before checking if it's a property. Fixes a conflict with Laravel's
__isset()
implementation onIlluminate\Database\Eloquent\Model
.
3.0.0 - July 20, 2017
- The resolver no longer accepts an object during instantiation. Instead, objects are passed directly to the
presenter()
andpresenterOrFail()
methods. - A new
resolveWith()
method on the resolver accepts a callable to customize resolution. - Support for the
presenterClass()
has been removed from the resolver in favor of the newresolveWith()
method on the resolver. You can pass a callable with your existingpresenterClass()
logic to the resolver instead. - Model access should only be done through the new
model()
method. Access to_model
has been disabled.
2.0.0 - February 12, 2017
- Replaced
Factory
with newResolver
class. - Removed dependency on
deefour\producer
- Removed
Presentable
contract - Simplified
README.md
1.0.0 - October 7, 2015
- Release 1.0.0.
0.8.0 - August 8, 2015
- Compat changes for updates to
deefour/producer
. - New
Factory
class is available to prevent the need to interact directly with the factory indeefour/producer
. - Abstracted presenter resolution out to new
deefour/producer
. - Removed the Laravel service provider and facade. The
'producer'
service indeefour/producer
should be used instead.
0.6.2 - June 5, 2015
- Now following PSR-2.
0.6.0 - May 24, 2015
- Removed
model()
method on base presenter. - Renamed
$model
property to$_model
to avoid conflicts with an actual model attribute with the name'model'
. - Presenters now only provide property access to public properties on the presenter.
- Prefixed API methods/properties with
_
on the base presenter to further avoid conflicts with attribute overrides. - Made
$_model
property public. - Updates to code formatting.
0.5.0 - April 27, 2015
- Snake-case to camel-case method conversions are now cached for performance
- Exceptions are no longer thrown for missing properties/methods. See
6f33dda
for an explanation.
0.4.0 - March 19, 2015
- Rename
presenter()
helper topresent()
- Remove
helpers.php
from Composer autoload. Developers should be able to choose whether these functions are included. - Cleaning docblocks.
- Type-hinting the presenter factory.
- Renaming
Presentable
trait toResolvesPresenters
to avoid naming conflict with\Deefour\Presenter\Contracts\Presentable
.
0.3.0 - March 16, 2015
- Allow presenters to be explicitly requested, bypassing the model default. For example
$article = new Article; echo get_class($article->presenter()); //=> 'ArticlePresenter' echo get_class($article->presenter(FeaturedArticlePresenter::class)); //=> 'FeaturedArticlePresenter'
0.2.3 - February 27, 2015
Illuminate\Support\Collection
instances and native PHP arrays can now be passed directly into thepresenter()
helper.
0.2.2 - February 20, 2015
- Updated support for Laravel's Eloquent relations. Relations are now fetched and converted to presenter-wrapped objects or collections when requested.
0.2.0 - February 5, 2015
- Fix service provider.
- Make global
presenter()
work with Laravel IoC container if it's available. - Move trait.
0.1.0 - November 21, 2014
- Initial release.
License
Copyright (c) 2014 Jason Daly (deefour). Released under the MIT License.