Giter Site home page Giter Site logo

presenter's Introduction

Presenter

Decorate your objects using presenters. Primarily to keep presentation logic out of your models.

Ping me @robboclancy for any urgent issues, github isn't always correctly notifying me.

This library provides a simple class to help make a Presenter for your objects or arrays. It also has little extras for use within Laravel with minimal extra code in your controllers (in most cases no extra code).

Tests

Table of Contents

Installation

composer require robclancy/presenter:^2.0

or

Add robclancy/presenter to the "require" section of your composer.json file.

"robclancy/presenter": "^2.0"

Run composer update to get the latest version of the package.

Laravel

This package comes with an optional service provider for Laravel 5.8+ < Laravel 10 so that you can automate some extra steps. You will need to have installed using the composer method above, then register the service provider with your application.

Open app/config/app.php and find the providers key. Add

'Robbo\Presenter\PresenterServiceProvider',

to the array at some point after

'Illuminate\View\ViewServiceProvider',

Now presenters will automatically be created if using the laravel method described below.

Usage

Presenter is a very simple class that overloads methods and variables so that you can add extra logic to your objects or arrays without adding view logic to areas like your models or controllers and also keeps any extra logic out of your views.

General Usage

Let's say you have a list of users and you want to generate a link to the profile of each user. Many people would just build the URL in the view, or worse, in the controller. To separate this logic we instead use a presenter. Let's assume we have a User class which simply has an id and username property. The presenter might look like this.

class UserPresenter extends Robbo\Presenter\Presenter {

	public function url()
	{
		return $this->id.'-'.$this->username;
	}
}

Now our view should receive an instance of this presenter which would be created with something like $user = new UserPresenter(new User);. If we want to link to the users page all we have to do is call $user->url(). Now you have good separation of logic and an easy little class you can modify to add properties to your User in all areas. However, you might not want to be calling methods like this, it could be inconsistent with what you are doing, or you might want the code to look a little cleaner. That is where methods with the present prefix come in. All we do is update the presenter to the following.

class UserPresenter extends Robbo\Presenter\Presenter {

	public function presentUrl()
	{
		return $this->id.'-'.$this->username;
	}
}

Now the presenter will call this new method when you execute $user->url. Further more you can access this method via ArrayAccess by calling $user['url']. More on ArrayAccess support below.

Manually Initiate

As mentioned in the above section to create a presenter you simply initiate with the new keyword and inject your object or array.

class User {
	// ...
}

class UserPresenter extends Robbo\Presenter\Presenter {

	// ...
}

$user = new User;

// handle stuff here

// make sure to "convert" to a presenter before the object gets to your views
$user = new UserPresenter($user);


// Can also create a presenter for arrays
$user = [
	'id' => 1,
	'username' => 'Robbo',
];

// same as before
$user = new UserPresenter($user);

Laravel Usage

If you are using laravel and have followed the above installation instructions you can use the provided interface Robbo\Presenter\PresentableInterface to automate turning your model instances into a Presenter from both collections and when a model is sent directly to the view.

What the service provider does is extend Laravel's view component with a step before the view object is created. This step turns anything that implements the PresentableInterface into a Presenter by calling ->getPresenter(). What this means is you don't need to add anything extra to your controllers to have your views using presenters for your objects.

For Example.

class UserPresenter extends Robbo\Presenter\Presenter {

	// ...
}

class User implements Robbo\Presenter\PresentableInterface {

	/**
	 * Return a created presenter.
	 *
	 * @return Robbo\Presenter\Presenter
	 */
	public function getPresenter()
	{
		return new UserPresenter($this);
	}
}

Now whenever your User model is sent to a view, in a collection, array or by itself it will be turned into a presenter using the provided getPresenter() method. So your controller will work with User and when you get to your view it will be working with UserPresenter with the internal object being User.

Array Usage

1.1.x introduces support for arrays. The Presenter will implement ArrayAccess so in your views you can access your variables with $presenter['variable'] if you want. But more importantly you can give the Presenter an array instead of an object. So you can use presenters to work with array data as well as objects.

For example.

$user = [
	'id' => 1,
	'username' => 'Robbo',
];

class UserPresenter extends Robbo\Presenter\Presenter {

	public function presentUrl()
	{
		// This will work exactly the same as previous examples
		return $this->id.'-'.$this->username;

		// You can also do this...
		return $this['id'].'-'.$this['username'];
	}
}

// Now we create a presenter much the same as before
$user = new UserPresenter($user);


// In our views we can use the $user as if it were still an array
echo 'Hello, ', $user['username'];

// Or even treat it like the object that it is
echo 'Hello, ', $user->username;

// And like other examples, we can present the url in the same way
echo 'The URL: ', $user->url;
echo 'And again: ', $user['url'];

Extending the Decorator

As of 1.2.x I have added in a decorator object. This object takes care of turning an object that has PresentableInterface into a Presenter. By default, this is done with Laravel's View objects. The reasoning behind a new class instead of the previous implementation is so it can be better tested and also to allow you to extend it. Here is an example of extending the Decorator so that instead of using the PresentableInterface and getPresenter() method you can use a public variable on the object called $presenter.

Note: these instructions are for Laravel usage.

First extend the decorator...

use Robbo\Presenter\Decorator as BaseDecorator;

class Decorator extends BaseDecorator {

	/*
     * If this variable implements Robbo\Presenter\PresentableInterface then turn it into a presenter.
     *
     * @param  mixed $value
     * @return mixed $value
    */
    public function decorate($value)
    {
    	if (is_object($value) and isset($value->presenter))
    	{
    		$presenter = $value->presenter;
    		return new $presenter;
    	}

    	return parent::decorate($value);
    }
}

To use your new decorator either add the following to start/global.php or into your own service provider.

// In start/global.php

App::make('presenter.decorator', App::share(function($app)
{
	$decorator = new Decorator;

	Robbo\Presenter\Presenter::setExtendedDecorator($decorator);
	return $decorator;
}));

// In a service provider's 'register' method

$this->app['presenter.decorator'] = $this->app->share(function($app)
{
	$decorator = new Decorator;

	Robbo\Presenter\Presenter::setExtendedDecorator($decorator);
	return $decorator;
});

And that is all there is to it. You can easily automate the creation of presenters to suit your workflow using this method.

Change Log

3.0

  • minimum PHP version is now 8.1
  • fixed PHP 8.1 ArrayAccess deprecations

2.0.1

  • branched off 2.x to support < PHP 8.1
  • modernized the repository somewhat
  • updated documentation

2.0.0

  • minimum Laravel version is now 5.8
  • minimum PHP version is now 7.1
  • improved the view factory integration which should be future proof

1.4.0

1.3.3

  • updated tests to better cover Laravel versions after 5.4 and changed the license to MIT

1.3.2

  • updated to work with laravel 5.4.x, only actual changes that aren't style or tests
  • added php-cs-fixer to update the code style
  • added examples to have tests for providers in a full app situation

1.3.1

  • updated to work with laravel 5.x

1.3.0

  • updated to work with laravel 4.2.x, to use in 4.1.x stay on version 1.2.*
  • moved to PSR-4 and now PHP 5.4+
  • small refactor and check isset against 'present' methods, thanks BenConstable

1.2.0

1.1.0

  • the Presenter class now implements ArrayAccess
  • added ability to use an array as your internal data

1.0.2

  • fixed bug caused by laravel update
  • added smarter converting of presenters from PresentableInterface'd objects
  • added object getter getObject to retrieve the internal object

1.0.1

  • fixed bug caused by laravel update

1.0.0

  • Initial Release

presenter's People

Contributors

4giedrius avatar alexwhitman avatar bafor avatar benconstable avatar hotmeteor avatar janhartigan avatar jenssegers avatar joaorobertopb avatar juco avatar lahaxearnaud avatar luisdalmolin avatar nyholm avatar pjona avatar prodeveloper avatar robclancy avatar robclancy-test avatar skovachev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

presenter's Issues

Can't get presenter to work the regular way

Hi,

I followed the examples exactly but for some reason I can't get things to work. The only way it works is to alter the syntax slightly, which I think is wrong?

  • Added serviceprovider to providers
  • Laravel 4.1
  • Presenter 1.2.*
// Article.php
class Article extends \Eloquent implements \Robbo\Presenter\PresentableInterface {
    public function getPresenter()
    {
        return new \ArticlePresenter($this);
    }
}
// ArticlePresenter.php
class ArticlePresenter extends \Robbo\Presenter\Presenter
{
    public function presentTitle()
    {
        /* note $this->object instead of just $this-> */
        return $this->object->title;
    }
}
// article.twig
{{ article.getPresenter().title }}

Am I missing something?

Q: Presenting on Objects from third-party Packages

Thank you for an excellent package. Looking forward to v2 with Traits.

One thing I've been trying to figure out is the best way to add a presenter to an object that's not from a model of my own, but from a Package or API call. For example, from the package Laravel/Cashier I get a Stripe customer object back from their API, I'd like to present the subscription end date. What I'm doing now is just newing up a presenter on it like $customer = new BillablePresenter($customer);. Any better solutions/ideas?

Environment Inheritance error

Hi there

Latest update from Taylor includes and extra parameter in Environment.php Make() so now getting the following error:

Runtime Notice: Declaration of BigElephant\Presenter\View\Environment::make() should be compatible with Illuminate\View\Environment::make($view, $data = Array, $mergeData = Array) in /Web/tgc/vendor/bigelephant/presenter/src/BigElephant/Presenter/View/Environment.php line 9

(Still using BigElephant)

Rich

Docs issue on Laravel example

Hi,

In the Example View says:

<ul>
@foreach ($recentTopics AS $topic)
    <li><a href="{{ $topic->url() }}">{{ $topic->title }}</a></li>
@endforeach
</ul>

but this results in an error because the method url() belongs to the Presenter not to the Model,
need to be changed to _{{ $topic->getPresenter()->url() }}_.

Correct me if I'm wrong.

I cant create a method to the field "Data_Nasc"

I have a field in my clients table, called "Data_Nasc".
But, I cant create a method to presenter this field in ClientePresenter.
I get it that when the field have underline, I need remove the underline and create the presenter method in came on case.
But, in this specific case? Data_Nasc. What I can do to workaround this?

Running with view composers

Is it not possible to use this presenter with Laravel View Composers? If I add the Robbo\Presenter\PresenterServiceProvider::class, service in my config/app.php file I keep getting an undefined variable error (where the variable is set using the view composer). When presenter service is commented out the composers work fine.

Running

  • Laravel v5.1
  • robclancy/presenter v1.3.1

Issue with a presenter using Form::model.

Hi there

I tried presenter these last few days.
So far so good but i have a problem with Form::model.

If i do this, with $profil an instance of a model with a presenter inplemented using the interface, my field name stay blank but the value exists in the model instance.

{{ Form::model($profil, array('method' => 'patch', 'class' => 'form-horizontal laravel-form', 'role' => 'form', 'route' => array('profils.update', $profil->id))) }}
    {{ Form::text('name') }}  
    {{ Form::submit('Enregistrer', ['class' => 'btn btn-success'] ) }}
{{ Form::close() }}

then if i do this to test,

{{ Form::model(Profil::find(10433), array('method' => 'patch', 'class' => 'form-horizontal laravel-form', 'role' => 'form', 'route' => array('profils.update', $profil->id))) }}
    {{ Form::text('name') }}  
    {{ Form::submit('Enregistrer', ['class' => 'btn btn-success'] ) }}
{{ Form::close() }}

the input field displays a value.
Then to be sure i tested again that way

{{ Form::model(Profil::find(10433)->getPresenter, array('method' => 'patch', 'class' => 'form-horizontal laravel-form', 'role' => 'form', 'route' => array('profils.update', $profil->id))) }}
    {{ Form::text('name') }}  
    {{ Form::submit('Enregistrer', ['class' => 'btn btn-success'] ) }}
{{ Form::close() }}

and i get a blank input field.
I'm very confused because so far i'm using Presenter on 2 models and the other one works fine when it comes to displaying edit form.

Do you have an idea where to look ?

How to use "Extending the Decorator"

Hi,

I was able to get the PresentableInterface and getPresenter() plugin style working but I am not able to get the "Extending the Decorator" style working.

Here is what I am doing :

  1. class User extends Eloquent {
    ....
    public $presenter = 'Sampleapp\Presenters\UserPresenter';
    // removed getPresenter()
    }
  2. Created the Decorator class
  3. Add to start/global.php

App::make('presenter.decorator', App::share(function($app)
{
$decorator = new (PATH TO DECORATOR CLASS)\Decorator;
Robbo\Presenter\Presenter::setExtendedDecorator($decorator);
return $decorator;
}));

Static model functions

Any reason for not using __callStatic in order to use the model static functions?

MyPresenter::find(1);

I've to separate the Presenters from the model as I'm having problems when using the Form::model($model...);

PHP 8.2 warnings

Hi, we recently updated our Laravel project to Laravel 10 and PHP8.2 and we are now getting the following warnings:

  • Return type of Robbo\Presenter\Presenter::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/app/current/vendor/robclancy/presenter/src/Presenter.php on line 124
  • Return type of Robbo\Presenter\Presenter::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/app/current/vendor/robclancy/presenter/src/Presenter.php on line 108
  • Return type of Robbo\Presenter\Presenter::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/app/current/vendor/robclancy/presenter/src/Presenter.php on line 97
  • Return type of Robbo\Presenter\Presenter::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/app/current/vendor/robclancy/presenter/src/Presenter.php on line 75

Nothing critical but would be great if they got fixed :)

Presenter breaking Laravel Form::model functionality

Hi Rob,

I believe Presenter is breaking Laravel Form::model functionality.

Controller:

$codesJob = $this->codesJob; // Model Object
// Inject default Title
$codesJob->title = 'My Codes Job Title';
// Pass Model with injected data
return View::make('system/codes_job/create', compact('codesJob'));

Model:

use Robbo\Presenter\PresentableInterface;
class CodesJob extends Eloquent implements PresentableInterface {
... 
}

View:

{{ Form::model($codesJob, array('route' => array('job.create'))) }}
{{ Form::text('title', null, array('class' => 'form-control')) }}

This only works when removing Presenter stuff from the Model.
ie. only have class CodesJob extends Eloquent

Any ideas on how I can keep Presenter and still have all of Laravel core functionality working?

Thank you :)

my presenter functions don't work

I'm trying to get this working but my presenter functions don't work. How do I go about debugging this?

This is how i pass the user object to my view and since I implemented getPresenter it seems that all that I should have to do? (using sentry 2)

$user = Sentry::getUser();
View::share('user', $user);

I've extended my model to

use Illuminate\Auth\UserInterface;
use Cartalyst\Sentry\Users\Eloquent\User as SentryUserModel;
use Robbo\Presenter\PresentableInterface;

class User extends SentryUserModel implements PresentableInterface  {
    /**
     * Return a created presenter.
     *
     * @return Robbo\Presenter\Presenter
     */
    public function getPresenter()
    {
        return new UserPresenter($this);
    }
I've changed the config (cartalyst/sentry/config.php) in setting to use my User model
'model' => 'User',

Nested presenters (a.k.a Presenting Eloquent relationships)

I'm using Presenter with some Eloquent models which works fine for the 'top-level' model, but relationships aren't converted to a presenter.

For example, if I have classes defined as:

class GenericPresenter extends Robbo\Presenter\Presenter {
    public function presentTest()
    {
        return 'test';
    }
}

class User extends Eloquent implements Robbo\Presenter\PresentableInterface {
    public function department()
    {
        return $this->hasOne('Department');
    }

    public function getPresenter()
    {
        return new GenericPresenter($this);
    }
}

class Department extends Eloquent implements Robbo\Presenter\PresentableInterface {
    public function getPresenter()
    {
        return new GenericPresenter($this);
    }
}

In a controller:

$users = User::with('department')->get();
return View::make('view')->with('users', $users);

In the view:

@foreach ($users as $user)
    {{{ $user->test }}} - {{{ $user->department->test }}}
@endforeach

prints test - because $user->department isn't an instance of GenericPresenter.

Now obviously my use case is Eloquent specific, but it would be useful if there was a way of defining relationships/attributes which should also be made presentable if they implement PresentableInterface.

Usage in the laravel API resource

Hi, How can I use presenter class in the API resource

Example:

  1. Resource class
class CouponResource extends Resource
{
    public function toArray($request): array
    {
        return [
            'name' => $this->name,
            'url' => $this->url(), // here I try to use the method from the presenter
        ];
    }
}
  1. Presenter
use Robbo\Presenter\Presenter;

class CouponPresenter extends Presenter
{
    public function url()
    {
        return $this->id . '-' . $this->name;
    }
}
  1. Controller
    public function list(Request $request): AnonymousResourceCollection
    {
        return CouponResource::collection(Coupon::paginate());
    }
  1. Model
class Coupon extends Model implements PresentableInterface
{
    public function getPresenter()
    {
        return new CouponPresenter($this);
    }
}

Presenter won't work on models that return mutiple rows.

I've tried 3 separate days, and each time i made sure the package was up to date, and started the Presenter class from scratch following the readme.

Example:

$account = Model::find(1);
$account = new ModelPresenter($account);

echo $account->url; // WORKS

//////////////

$account = Model::where(...)->get();
$account = new ModelPresenter($account);

foreach ($accounts as $account) {
    echo $account->url; // NOTHING OUTPUT
}

Fatal error Method Robbo\Presenter\View\View::__toString() must not throw an exception

I have a method in my model

  public function posts() {
    return Post::where('receiver_id', $this->id);
  }

From my view when I call $user->posts I get

PHP Fatal error: Method Robbo\Presenter\View\View::__toString() must not throw an exception

And it hangs my entire apache on windows.

The only way I got this to work is if I change my posts function in the model to

  public function posts() {
    return Post::where('receiver_id', $this->id)->get();
  }

and my call in the view to $user->posts()

Form::model()

I've realized I cannot use a presenter object with the Form::model method. It throws an error like "Not getting an Eloquent class".

Anything to do with this?

L4 resourceful controllers implementation?

I tried installing it in a L4 app with resourceful controllers and followed documentation's steps.

  1. Required the package in my composer and added it to config/app.php providers.

  2. Created app/presenters folder.

  3. Created app/presenters/ProjectPresenters.php containing this code:

    <?php
    
    use Robbo\Presenter\Presenter;
    
    class ProjectPresenter extends Presenter
    {
    }
  4. Added

    class Project extends Eloquent implements PresentableInterface {
    ...
    function getPresenter()  
    { 
      return new ProjectPresenter($this);  
    }

    to my Project model.

  5. And here's what I have in my controller:

    public function index()
    {
      return View::make('site/projects/index')
        ->with('projects', Project::all());
    }

But I keep on getting Class 'ProjectPresenter' not found and with this code highlighted:

public function getPresenter()
{
  return new ProjectPresenter($this);
}

The docs seems to contain L3 code but as I understood L4 is supported. Am I missing something?

Policy/Gate permission checking with "can" always false

Not sure if this is a bug or not. You can close it without any explanation if you think so.

First, our stack is a bit odd because we use TwigBridge and twig templates and we haven't checked the following issue on blade .

{{auth_user().can('update', item)}} returns always false.

Further investigation we discovered that check is false because the DI cannot resolve item as item is a Presentable instance.

As object attribute is protected we cannot straight forward do {{auth_user().can('update', item.object)}}. For this to work had created a trait with method:

public function presentObject()
{
    return $this->object;
}

Now {{auth_user().can('update', item.object)}} works properly.

Facades in Presenter

Hi, is it possible to use facades in presenter class, or I am missing something? Like this:

Factory not found

[RuntimeException]
Error Output: PHP Fatal error: Class 'Illuminate\View\Factory' not found in /Users/Ken/Sites/conduitweb/vendor/robclancy/presenter/src/View/Factory.php on line 11

Does Laravel 5.1 work?

I used presenter in my laravel 4.2 project , but these day I want to upgrade my project to L5.1 . I also found that your commit common say something about L5, is that means you had upgrade presenter ?

Update for 4.2

Since you're doing new View\Environment in the service provider and this class was renamed, it's all crashing n stuff on 4.2.

convert array of user presenter to json

I'm trying to encode my user object to json so I can pass it to my javascript variable. The problem since it is getting converted to a UserPresenter in my view I'm having some issues.

For one user I can just do $user->getObject() and that get's properly encoded in my view however I have a problem when it's a collection or array of users (essentiall UserPresenters in the view), how would i do it in a clean way?

For one user I do $user->getObject()

$user = User::first();
$users = User::all();

<script>
var obj = {
    user: <?php echo $user->getObject(); ?>,
}
</script>

result

{"id":1,"first_name":"A","last_name":"A","birthday":"1970-02-12","gender":"M","username":"aa","email":"[email protected]","permissions":null,"activated":true,"activated_at":null}

However how do I do it if it's a collection of users?

$users = User::all();

<script>
var obj = {
    users:<?php echo $users; ?>,
}
</script>

result

users: [{},{}];

This is the only way I got it working but it's so nasty

<script>
var obj = {
    users: [<?php for($i = 0; $i < count($users); $i++) { echo $users[$i]->getObject(); if ($i + 1 < count($users)) { echo ','; } } ?>]
}
</script>

result

users: [{"id":1,"first_name":"A","last_name":"A","birthday":"1970-02-12","gender":"M","username":"aa","email":"[email protected]","permissions":null,"activated":true,"activated_at":null},{"id":2,"first_name":"b","last_name":"b","birthday":"1982-01-10","gender":"M","username":"bb","email":"[email protected]"}]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.