Presenters
Introduction
Presenters help you organize view layer (presentation) logic for objects. Logic in the presenter
should only be for display purposes.
An important secondary purpose for presenters is to map the decorated object's API in a clean way for views.
For example, instead of requiring verbose code like this:
{{ person.getRelation('cousins').first().first_name }}
Presenters help you with much cleaner syntax:
{{ person.cousins.first.first_name }}
All presentable objects including entry models and collections are automatically decorated with a presenter in the view layer.{.notice}
Basic Usage
The \Robbo\Presenter\PresentableInterface
interface tells the Streams Platform that the class is presentable
and has a getPresenter
method. By default getPresenter
return an instance of \Anomaly\Streams\Platform\Support\Presenter
or the nearest presenter to the class.
public function newPresenter()
{
return new Presenter($this);
}
getObject
The getObject
returns the decorated object instance from a presenter.
$presenter->getObject();
{{ presenter.getObject() }}
This method is very useful when working with API objects in the view layer where the instance is generally required and not the presenter.{.tip}
__get
The __get
magic method is invoked any time you attempt to access a decorated object's attribute. However you can also lazy-call methods (with no arguments) in this way.
Below we outline how you can leverage __get
behavior to achieve clean view syntax. Procedure is listed in order of priority and the first matching scenario will be used.
Check the presenter for a method where the method is the camel case of the {attribute_name}
.
$presenter->display_name; // Returns: $presenter->displayName();
{{ presenter.display_name }} // Returns: {{ presenter.displayName() }}
Check the presenter for a getter method where the method is the camel case of get_{attribute_name}
.
$presenter->display_name; // Returns: $presenter->getDisplayName();
{{ presenter.display_name }} // Returns: {{ presenter.getDisplayName() }}
Check the presenter for a getter method where the method is the camel case of is_{attribute_name}
.
$presenter->enabled; // Returns: $presenter->isEnabled();
{{ presenter.enabled }} // Returns: {{ presenter.isEnabled() }}
Check the decorated object for a getter method where the method is the camel case of get_{attribute_name}
.
$presenter->display_name; // Returns: $presenter->getObject()->getDisplayName();
{{ presenter.display_name }} // Returns: {{ presenter.getObject().getDisplayName() }}
Check the decorated object for a boolean getter method where the method is the camel case of is_{attribute_name}
.
$presenter->enabled; // Returns: $presenter->getObject()->isEnabled();
{{ presenter.enabled }} // Returns: {{ presenter.getObject().isEnabled() }}
Check the decorated object for a method where the method is the camel case of the {attribute_name}
.
$presenter->display_name; // Returns: $presenter->getObject()->displayName();
{{ presenter.display_name }} // Returns: {{ presenter.getObject().displayName() }}
Check the decorated object for a getter style hook where the hook is get_{attribute_name}
.
$presenter->display_name; // Returns: $presenter->getObject()->call('get_display_name');
{{ presenter.display_name }} // Returns: {{ presenter.getObject().call('get_display_name') }}
Check the decorated object for a standard hook where the hook is {attribute_name}
.
$presenter->display_name; // Returns: $presenter->getObject()->call('display_name');
{{ presenter.display_name }} // Returns: {{ presenter.getObject().call('display_name') }}
Return the decorated object's attribute named {attribute_name}
.
$presenter->display_name; // Returns: $presenter->getObject()->display_name;
{{ presenter.display_name }} // Returns: {{ presenter.getObject().display_name }}
__call
The __call
magic method will try running the method
on the decorated object. Remember the __call
function is only invoked when the method does not exist on the presenter itself. So this effectively maps decorated object methods to the presenter.
Presenters globally block calls to delete, save, and update.{.important}
Manually Decorating Objects
You can manually decorate
an object with the \Anomaly\Streams\Platform\Support\Decorator
class.
Decorator::decorate()
The decorate
method decorates the object. It can also decorate an entire collection of presentable items.
Returns: \Anomaly\Streams\Platform\Support\Presenter
or \Illuminate\Database\Eloquent\Collection
Arguments
Key | Required | Type | Default | Description |
---|---|---|---|---|
$value |
true |
mixed |
none |
The presentable object or collection of presentable items. |
Example
$decorated = $decorator->decorate($model);
Non-presentable values will be returned as is.{.notice}
Undecorating Values
The \Anomaly\Streams\Platform\Support\Decorator
class can also be used for obtaining the original undecorated values.
Decorator::undecorate()
The undecorate
reverses the decoration method's action. This is helpful for wrapping API methods for objects inside the view layer.
Returns: mixed
Arguments
Key | Required | Type | Default | Description |
---|---|---|---|---|
$value |
true |
mixed |
none |
The presenter or collection of presenters. |
Example
$original = $decorator->undecorate($model);
Non-presentable values will be returned as is.{.notice}
Writing Presenters
Presenters are automatically generated for you when using the make stream command:
php artisan make:stream stream_slug example.module.test
You can also create your own addon presenter in most cases by transforming the model class name into it's corresponding presenter class name:
TestModel -> TestPresenter
In all other cases all you need to do is define your own newPresenter
method on the presentable
class:
<?php namespace Anomaly\ExampleModule\Test;
use Robbo\Presenter\PresentableInterface;
use Anomaly\Streams\Platform\Support\Presenter;
class TestObject implements PresenterInterface
{
/**
* Return the presenter.
*
* @return string
*/
public function newPresenter()
{
return new Presenter($this);
}
}
An example presenter looks like this:
<?php namespace Anomaly\ExampleModule\Test;
use Anomaly\Streams\Platform\Support\Presenter;
class TestPresenter extends Presenter
{
/**
* Return the full name.
*
* @return string
*/
public function name()
{
return implode(' ', array_filter([$this->object->getFirstName(), $this->object->getLastName()]));
}
}
Now you can use this method in your API:
$test->name();
Or view layer:
{{ test.name() }}