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() }}