Comunidad de diseño web y desarrollo en internet online

El patrón MVC y sus alternativas

El patrón MVC es uno de los más populares hoy en día. Gracias al patrón y los frameworks que lo soportan, como vimos en el artículo anterior, muy probablemente tu código pasó de esto:

Código :

<?php $result = mysql_query("SELECT * from users"); ?>
<h1>Usuarios</h1>
<ul>
<?php while ($user = mysql_fetch_obj($result)): ?>
    <li><?php echo $user->name ?></li>
<?php endwhile; ?>
</ul>


A algo como esto:

Código :

<?php
// Controlador:
class UsersController extends BaseController {
    
    public function index()
    {
        // Llamada al Modelo:
        $users = User::all();
        // Llamada a la Vista:
        View::make('users/list', compact($users));
    }


Código :

<!-- Vista -->
<ul>
@foreach($users as $user)
    <li>{{ $user->name }}</li>
@endforeach
</ul>


Pero como en cualquier otra actividad, una vez que dominas cierto nivel comienzas a hacerte preguntas, ya no es ¿Cómo hago esto? Sino ¿Será ésta la mejor forma de hacerlo? Lo que pasa por tu mente. Por ejemplo, una pregunta que quizás te has hecho:

¿En que capa valido los datos: en el modelo o en el controlador?

Pero antes de responderte vamos a rebobinar un poco:

¿Qué es MVC?


Según Wikipedia, el MVC es una forma de separar tu aplicación en tres capas: Modelo, Vista y Controlador. Fue presentado en los años 70, muchísimo antes que PHP, que Ruby, que la Web y antes de tí.

MVC y la Web



Como ves el MVC no fue hecho para la web, ni al revés tampoco.

La web se basa en el protocolo HTTP, como les expliqué en el tutorial Primeros pasos con Laravel:



El usuario solicita algo (Request) a un servidor que lo procesa (valida, envía o pide datos a la base de datos, etc.) y devuelve una respuesta (Response).

Todo esto nos lleva a una primera conclusión interesante:

La versión (o versiones) que implementan los frameworks Web sobre MVC son simples adaptaciones del patrón original.


¿Por qué los frameworks web implementan MVC?


MVC es una forma muy fácil de separar los componentes de tu aplicación. Sobretodo si nunca has trabajado con frameworks, MVC es una forma fácil de comenzar. Igualmente si has trabajado con un framework y quieres comenzar con otro, te sentirás “en casa” si consigues un patrón común a ambos.

1. La Web no se trata de MVC[/b] y 2. Usas una versión “modificada” de MVC en conclusión: MVC es una forma inicial organizarte pero tu desarrollo no depende de este patrón.

Si ya tienes tiempo programando, terminas proyectos sencillos con facilidad pero desde hace un tiempo te toca encargarte de proyectos más complejos, muy probablemente:

Las capas del MVC se ha vuelto para ti como 3 grandes muros en las que tus habilidades como programador viven encerradas, por miedo a que tus proyectos se vuelvan demasiado grandes, incontrolables, te atrapen y te coman vivo.



Pero cada vez que te sorprendes dudando si colocar tus reglas de validación en el modelo o en el controlador o en otro lado, es esa parte de ti encerrada en el muro que se pregunta cómo será la vida en otros lugares donde hay mares, desiertos, grandes montañas y lagos congelados.

Pero de nuevo, antes de responder tu pregunta rebobinemos una vez más.

Información que podemos compartir hasta ahora sobre el MVC y la Web



El muro o La Capa Controlador

Uno de los conceptos genéricos: El controlador es un intermediario, entre los datos que se envían al consumidor/usuario (vista) y los datos que produce (obtiene o guarda) el modelo.

En términos de la Web como tal, el controlador recibe la petición HTTP del usuario (Request) y, luego de comunicarse con el o los modelos para obtener/validar/guardar/exportar la data adecuada, envía una respuesta igualmente HTTP (Response) que puede ser datos en formato JSON, una vista HTML o una redirección a otro controlador o sitio web.

El controlador: Recibe, Invoca, Responde. Nada más. Repite conmigo: El controlador recibe, invoca, responde, el controlador recibe, invoca, responde, el controlador…

Programador :

”¿Entonces la validación va en el modelo, verdad?” Sí, pero continuemos...


Piensa en el controlador como el intermediario entre el protocolo HTTP y tu aplicación, tu aplicación DEBE estar en algún otro lugar fuera del controlador.

Imagina que tu cliente te pide crear una aplicación para iOS basada en su sitio web. Al cambiar la interfaz, los controladores tendrán que cambiar también, así que decides crear una nueva carpeta: app/controllers/ios/ para alojar tus nuevos controladores y otra app/views/ios/ para tus vistas.

¿Cuánto código de tus controladores originales tienes que copiar/pegar a tus controladores nuevos para crear esta nueva aplicación? Si la respuesta tiende a 0 ¡Vas muy bien! Si la sola idea te hace sudar frío, definitivamente estás leyendo el artículo correcto.

La Vista

Esta es fácil: la vista es la presentación, la capa visual, la interfaz de usuario.



En la web la vista es el HTML generado dinámicamente con los datos que se reciben del controlador o directamente del modelo.

Si tu aplicación es AJAX, por ejemplo, un web service que permite que aplicaciones de terceros obtengan datos sobre el clima de las islas de Okinawa, entonces gran parte de tu aplicación enviará datos en formato JSON (ejemplo) y no tendrá como tal una capa Vista.

Si te “preocupa” que esto “rompa” el muro o patrón MVC, nuevamente estás leyendo el post correcto.



El Modelo

Acá es donde todo se complica. Mientras que el controlador y la vista son conceptos fáciles de definir y entender, el modelo no.

La misma Wikipedia habla del modelo como la capa de acceso a la base de datos. Es decir el modelo se comunica con la base de datos. Pero nuestra aplicación necesita mucho más que el simple acceso a una base de datos.

Es acá donde también te dicen que el modelo engloba la “lógica de negocios” de tu aplicación.

¿Qué rayos es la lógica del negocio?


Yo leí ese concepto en el 2006 y hoy, año 2013, a pocos días para el 2014, al fin puedo decir que entiendo de qué se trata.

El concepto realmente no es tan complicado, imagina una tienda de ropa. Los anfitriones que te reciben en la puerta y te preguntan qué deseas vendrían siendo las rutas, los vendedores y la caja que cobra las prendas que compraste o acepta devoluciones serían los controladores. La vista es donde se exhibe la mercancía. El modelo es “todo lo demás”.

Son parte del modelo, por ejemplo:

  1. Las reglas de la gerencia sobre cuando aceptar o no una devolución.
  2. El contador y los libros de contabilidad (esto sería relativo a la base de datos)
  3. Solicitar ropa a los proveedores (datos a una API externa)
  4. El departamento de marketing que establece promociones de 20% en mercancía seleccionada, descuentos de fin de temporada (cuándo termina una temporada y comienza otra, qué ropa pertenece a qué temparada, el porcentaje de descuento, etc.)
  5. ¿Qué otros ejemplos puedes imaginarte que serían parte del modelo?


Aunque sea una tienda (una aplicación) pequeña hay que hacer muchísimas más operaciones no vinculadas directamente a guardar o mostrar datos en una base de datos.

Programador :

Sí, tienes razón, y parece demasiado como para encapsular todo eso en una sola capa ¿No?


El problema de querer solucionar todo bajo un simple enfoque MVC


Muchos programadores creen que “el modelo es la base de datos” (representada como objetos u ORM) y todo lo que NO corresponda a la base de datos, como por ejemplo, las reglas para validar un usuario, lo terminan colocando en el controlador, creando así un nuevo patrón de “super controlador” nada reusable.

Otros programadores que entienden que “el modelo es la lógica de la aplicación” agrupan todo lo descrito arriba (contabilidad, inventario, promociones, facturación, etc.) en las clases en las que se divide el modelo (usualmente una por cada tabla de la base de datos) Entonces un sólo modelo, por ejemplo, la clase User.php puede terminar agrupando 1000 líneas de código.

Hay un tercer grupo que mezcla los 2 párrafos anteriores.

Alternativas al MVC


Este contenido es muy extenso para cubrirlo acá en un solo artículo, por otro lado es difícil que cambies de paradigma de la noche a la mañana, sin embargo, te daré algunos “tips” para que, como dice el título, seas mejor programador:

No tengas miedo de crear capas adicionales según las necesidades de tu aplicación


Dicho de otra forma: No te encasilles en que todo debe ir en el modelo o en la vista o en el controlador.

Imagina que tu cliente te pide que la data de su aplicación sea exportable a CSV, PDF, Excel y otros formatos. ¿En dónde iría esto? En Laravel y otros frameworks el modelo está relacionado con la base de datos, y en el controlador sólo debería ir el llamado a la función que exporte dichos datos. Solución: crea otra capa.

Crea una carpeta adicional, por ejemplo: app/models/csv/ allí vas a crear un modelo base para exportar CSV:

Código :

// app/models/csv/BaseCsv.php
<?php namespace Csv;

abstract class BaseCsv
{
    
    protected $model;
    
    abstract public function getFilename();
    
    abstract public function getData();
    
    public function __construct(\Eloquent $model)
    {
        $this->model = $model;
    }
    
    public function getHeaders()
    {
        return array(
            'Content-Type'        => 'text/csv; charset=utf-8',
            'Content-Disposition' => 'attachment; filename="' . $this->getFilename() . '.csv"',
            'Pragma'              => 'no-cache'
            );
    }
    
    public function export()
    {
        $csv = \BasicCsv::export($this->getData());
        return \Response::make($csv, 200, $this->getHeaders());
    }
    
};


Este nuevo “modelo” tiene algunas funciones básicas para exportar un CSV. Lo defino como una clase “abstracta” para obligar a los programadores a extenderla en una clase hija que implemente las funciones para obtener el nombre de archivo a generar (getFilename) y, por supuesto, la data que será exportada al CSV (getData).

Si no sabes qué es una clase abstracta, lee este tutorial, que, por cierto, tiene un ejemplo interesante de cómo crear un “modelo” para el manejo de imágenes.

Ahora, para exportar los usuarios, como lo pidió nuestro cliente, extendemos la clase BaseCsv de esta forma:

Código :

<?php namespace Csv;

class UserCsv extends BaseCsv {
    
    public function getFilename()
    {
        return Str::slug($this->model->full_name . ' ' . date('d-M-Y'));
    }
    
    public function getData()
    {
        $users = $this->model->all();
        
        $data = array();
        
        $data[] = array('ID', 'Name');
        
        foreach ($users as $user)
        {
            $data[] = array($user->id, $user->full_name);
        }
        
        return $data;
    }
    
}


Vean cómo defino en UserCsv, el contenido de las funciones declaradas abstractas en el modelo BaseCsv. Ahora en nuestro controlador tendríamos el siguiente código, muy limpio, que sólo hace la llamada a nuestro nuevo modelo:

Código :

<?php

class UsersController extends Controller {
    
    // ...otros metodos aqui
    
    public function csv()
    {
        $userCsv = new Csv\UserCsv(new User());
        return $userCsv->export();
    }
}


Nuestra ruta sería algo como esto:

Código :

Route::get('users/csv', array('as' => 'users.csv', 'uses' => 'UsersController@csv'));


Por último les dejo la clase básica que usé exportar CSV en el modelo BaseCsv:

Código :

<?php

class BasicCsv {

    public static function import($filePath, $delimiter = ",")
    {
        $data = false;

        ini_set('auto_detect_line_endings', true);

        if (is_file($filePath) AND is_readable($filePath)) {
            if (($handle = fopen($filePath, "r")) !== false) {
                $data = array();

                while (($line = fgetcsv($handle, null, $delimiter)) !== false) {
                    $data[] = $line;
                }
            }
        }

        return $data;
    }

    public static function export($data, $delimiter = ";")
    {
        ob_start();

        $fp = fopen("php://output", 'w');

        foreach ($data as $row) {
            fputcsv($fp, $row, $delimiter, '"');
        }

        fclose($fp);

        return ob_get_clean();
    }

}


Patrones adicionales



Middle-man

Se refiere a clases que están en el medio de código de bajo nivel (por ejemplo SQL) y código de alto nivel (por ejemplo el autenticador de usuarios). En este caso el modelo BaseCsv está en el medio entre el código de “bajo nivel” para generar un CSV en PHP y el código de alto nivel brindado por el ORM Eloquent para manejar la data de los usuarios.

Facade

Es un patrón para hacer una interfaz complicada fácil de usar, en tu modelo User.php podrías hacer lo siguiente:

Código :

<?php

class User extends Eloquent {

    // ...Codigo aqui
    
    public static function getCsv()
    {
        $userCsv = new Csv\UserCsv(new static);
        return $userCsv->export();
    }
    
    
    // Codigo aqui...

}


Y ahora en el controlador:

Código :

public function csv()
{
    return User::getCsv();
}


De esta forma, aunque tu aplicación se complique mucho, la forma de acceder a ella desde el controlador y la vista seguirá siendo sencilla. Este patrón se aplica en la versión 4 de Laravel. Veamos otras capas adicionales:

Repositorios


¿Necesitas construir muchos queries para acceder a tus objetos de eloquent? Agrúpalos en “repositorios”, por ejemplo:

Código :

<?php

class UserRepo extends BaseRepo {
    
    public static function findByGender($gender)
    {
        return User::where('gender', $gender)->get();
    }
    
    public static function findAdminUsers()
    {
        return User::where('type', 'admin')->get();
    }
    
    public static function findActiveUsers()
    {
        return User::where('last_login', '>=', DB::raw('DATE_SUB(NOW(), INTERVAL 5 MINUTE)'))
                ->orderBy('last_login', 'DESC')
                ->get();
    }
    
}


De manera que tus modelos de Eloquent queden más “limpios” ya que en ellos ahora sólo vas a definir atributos (ej.: getFullnameAttribute), relaciones (hasMany, belongsTo), y facades a otros métodos.

Validadores


Respondiendo a tu duda de antes, la validación forma parte del modelo, pero no necesariamente debe estar dentro de tu objeto ORM. Puedes crear una capa de validación en algún lugar de tu aplicación, partiendo de un objeto validador “base” así:

Código :

<?php

abstract class BaseValidator
{

    abstract $rules;

    protected $model;
    protected $errors;
    
    public function __construct(\Eloquent $model)
    {
        $this->model = $model;
    }
    
    public function getRules()
    {
        return $this->rules;
    }
        
    public function getCreateRules()
    {
        return $this->getRules();
    }
    
    public function getUpdateRules()
    {
        return $this->getRules();
    }
    
    public function getErrors()
    {
        return $this->errors;
    }

    public function isValid($data)
    {
        if ($this->model->exists)
        {
            $rules = $this->getUpdateRules();
        }
        else
        {
            $rules = $this->getCreateRules();
        }
        
        $validation = \Validator::make($data, $rules);

        if ($validation->passes()) return true;

        $this->errors = $validation->messages();

        return false;
    }
    
    public function save($data)
    {
        if ( ! $this->isValid($data)) return false;
        
        $this->model->fill($data);
        $this->model->save();
        
        return true;
    }

    public function getErrors()
    {
        return $this->errors;
    }

}


Que luego la puedas extender así:

Código :

<?php

class UserValidator extends BaseValidator {
    
    protected $rules = array(
        'email'     => 'required|email|unique:users,email',
        'full_name' => 'required|min:4|max:40',
        'password'  => 'min:8|confirmed'
    );

    public function getRulesForUpdate()
    {
        $rules = $this->getRules();
        
        if (isset ($rules['email']))
        {
            // Si el usuario existe: Excluimos su ID de la regla "unique" (definida al final de la cadena)
            $rules['email'] .= ',' . $this->model->id;
        }

        return $rules;
    }
    
    public function getRulesForCreate()
    {
        if (isset ($rules['password']))
        {
            // Si el usuario no existe la clave es obligatoria:
            $rules['password'] .= '|required';
        }

        return $rules;
    }
    
}


Ahora para validar la data de un modelo en el controlador sólo escribirías algo como esto:

Código :

    public function edit($id)
    {
        $user = User::find($id);
        $validator = new Validator\UserValidator($user);
        $data = Input::only(array('email', 'full_name', 'password', ‘password_confirmation’));
        if ($validator->save($data))
        {
            Session::set('success', 'Success message!');
            return Redirect::to('user.show', $user->id);
        }
        else
        {
            return Redirect::to('user.edit', $user->id)->withErrors($validator->getErrors());
        }
    }


Los limites son: los requerimientos de tu aplicación + tus conocimientos actuales + tu creatividad, no la obediencia ciega de un patrón de los años 70. :crap:

En este tutorial simplifiqué algunos ejemplos usando funciones/métodos estáticos, más adelante quiero escribir sobre patrones más avanzados como la inyección de dependencias (IoC).

Por lo pronto espero que este tutorial te haya servido para dar un paso más hacia adelante en el camino infinito para ser un mejor programador.

Sígueme en Twitter: @Sileence para mantenerte informado sobre más artículos gratuitos de programación o consultar tus dudas.

Feliz navidad y prospero año nuevo 2014 a todos. Un abrazo. :)

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate