Pacuna's Blog

Usando decoradores en Laravel 4

En este tutorial mostrar茅 como podemos usar un decorador simple para evitar ensuciar nuestros modelos con funciones que s贸lo son utilizadas en nuestras vistas. Usar decoradores es una buena alternativa a las t铆picas funciones 鈥榟elpers鈥 que utilizamos en las vistas, pero que mezclan lo funcional, con el enfoque orientado a objetos que deseamos seguir. En cambio podemos construir una clase que va a 鈥榙ecorar鈥 nuestro modelo con funciones que podemos usar en la vista.

Problema

Supongamos que tenemos un modelo Post. En este post tenemos una funci贸n que nos indica si un post es nuevo o no. De acuerdo a esto, si el post es nuevo, ser谩 desplegado en la p谩gina principal de nuestra vista. Dejaremos la implementaci贸n de lado ya que no es lo importante aqu铆. Por ahora s贸lo retornaremos true para ver que todo funcione correctamente.

<?php

class Post extends Eloquent{
  protected $guard = [];
  public function isNew(){

  // implementar funcion
  return true;
  }
}

Esta funci贸n la podemos usar en nuestra vista luego de pasar una instancia de post desde nuestro controlador:

//show.blade.php
@if($post->isNew())
{{ $post->title }}
@endif

En la vista show, estamos desplegando el t铆tulo del post si es que fu茅 creado recientemente.

Como mencion茅 anteriormente, esta pr谩ctica agrega m茅todos que no corresponden a nuestro modelo y adem谩s no escalan de buena manera al ir aumentando el n煤mero de m茅todos que llamamos en nuestras vistas.

Soluci贸n: usar un Decorador

El decorador s贸lo ser谩 un contenedor de la clase Post, pero la 鈥榙ecorar谩鈥 con los m茅todos que necesitemos en nuestra vista. Para empezar vamos a crear un directorio para los decoradores en app/decorators. Dentro de este directorio crearemos el decorador para la clase Post. Para autocargar este directorio, una alternativa r谩pida es agregarlo en el autolader de tu composer.json:

 "autoload": {
 "classmap": [
 "app/commands",
 "app/controllers",
 "app/decorators",
 "app/models",
 "app/database/migrations",
 "app/database/seeds",
 "app/tests/TestCase.php"
鈥

Y luego ejecutar composer dump-autoload para cargar los archivos.

Implementaci贸n del decorador

Vamos por parte. En primer lugar, nuestro decorador necesita recibir la clase que est谩 conteniendo. Esto lo hacemos en el constructor:

<?php

public function __construct($post)
{
  $this->post = $post;
}

En esta clase podemos agregar los m茅todos que requerimos en la vista, por ejemplo isNew():

<?php

public function isNew()
{
  // sin implementacion
  return true;
}

El primer problema que se presenta, es que si queremos acceder desde nuestro decorador a los m茅todos y atributos del modelo que contiene, estos no estar谩n definidos. Lo que queremos es que en nuestra vista podamos hacer llamadas del tipo: $postDecorator->title, o $postDecorator->algunMetodo() siendo title y algunMetodo() de la clase Post, no de la clase PostDecorator. Para lograr esto hacemos uso de las funciones m谩gicas __call y __get de PHP. La funci贸n __call ser谩 ejecutada cada vez que llamemos a un m茅todo que no exista en la clase, mientras que __get ser谩 llamado cada vez que llamemos a alg煤n atributo que no exista en la clase.

<?php

// se ejecuta cada vez que no encuentra el metodo
public function __call($name, $arguments)
{
  //llamados a la funcion con el mismo nombre pero en la clase que contiene
  call_user_func_array([$this->post, $name], $arguments);
}
//se ejecuta cada vez que no encuentra el atributo
public function __get($name)
{
  //llamamos al atributo pero en clase que contiene
  return $this->post->$name;
}

De esta manera nuestra clase decoradora queda as铆:

<?php

class PostDecorator{
  protected $post;
  public function __construct($post)
  {
   $this->post = $post;
  }

  public function __call($name, $arguments)
  {
   call_user_func_array([$this->post, $name], $arguments);
  }

  public function __get($name)
  {
   return $this->post->$name;
  }

  public function isNew()
  {
  //implementacion
   return true;
  }
}

Ahora para usar esta clase en nuestro controlador debemos instanciarla y pasarle el objeto Post correspondiente:

<?php

// app/controllers/PostsController.php
class PostsController extends BaseController
{
  public function show($id)
  {
   $post = Post::find($id);
   $postDecorator = new PostDecorator($post);
   return View::make('posts.show')->with('postDecorator', $postDecorator);
  }
}

Como puedes ver, ahora en vez de enviarle una instancia de Post a la vista, le estamos enviando una instancia de PostDecorator.

Finalmente en la vista podemos usar la funci贸n isNew(), que ahora est谩 en nuestro decorador, y adem谩s podemos llamar a los atributos del post, pero a trav茅s del decorador.

@if($postDecorator->isNew())
 {{ $postDecorator->title }}
@endif

Evitando duplicaci贸n entre decoradores

Una mejora a este esquema ser铆a crear una clase decoradora base, la cual implemente los m茅todos que probablemente necesitaras en todos tus decoradores, como __call y __get. Luego cada decorador puede extender esta clase y as铆 evitas duplicaci贸n de c贸digo.

Podemos llamar a esta clase: BaseDecorator:

//app/decorators/BaseDecorator.php
<?php
abstract class BaseDecorator{
  protected $model;
  public function __construct($model)
  {
   $this->model = $model;
  }
  public function __call($name, $arguments)
  {
   call_user_func_array([$this->model, $name], $arguments);
  }
  public function __get($name)
  {
    return $this->model->$name;
  }

Y luego nuestra clase PostDecorator puede implementarla y heredar谩 los m茅todos __call y __get. Recuerda que para inicializar la clase abstracta debemos llamar a parent::construct en cada constructor de clase que la extienda.

//app/decorators/PostDecorator.php
<?php
class PostDecorator extends BaseDecorator{
  protected $post;
  public function __construct($post)
  {
   parent::__construct($post);
   $this->post = $post;
  }
  public function isNew()
  {
  //sin implementacion
   return true;
  }
}

Con esto ya tienes un esquema b谩sico para implementar tus propios decoradores. Si tienes dudas deja un comentario y lo responder茅 a la brevedad.

Saludos!

View original

#php #laravel

- 1 toasts