Patrón MVC Modelo Vista Controlador en PHP


El patrón clásico del diseño web conocido como arquitectura MVC, está formado por tres niveles:








1. El modelo representa la información con la que trabaja la aplicación, es decir, su lógica de negocio.
2. La vista transforma el modelo en una página web que permite al usuario interactuar con ella.
3. El controlador se encarga de procesar las interacciones del usuario y realiza los cambios apropiados en el modelo o en la vista.
        La arquitectura MVC separa la lógica de negocio (el modelo) y la presentación (la vista) por lo que se consigue un mantenimiento más sencillo de las aplicaciones. Si por ejemplo una misma aplicación debe ejecutarse tanto en un navegador estándar como un navegador de un dispositivo móvil, solamente es necesario crear una vista nueva para cada dispositivo; manteniendo el controlador y el modelo original. El controlador se encarga de aislar al modelo y a la vista de los detalles del protocolo utilizado para las peticiones (HTTP, consola de comandos, email, etc.). El modelo se encarga de la abstracción de la lógica relacionada con los datos, haciendo que la vista y las acciones sean independientes de, por ejemplo, el tipo de gestor de bases de datos utilizado por la aplicación.



        Las capas de la arquitectura MVC

        Para poder entender las ventajas de utilizar el patrón MVC, se va a transformar una aplicación simple realizada con PHP en una aplicación que sigue la arquitectura MVC. Un buen ejemplo para ilustrar esta explicación es el de mostrar una lista con las últimas entradas o artículos de un blog como este mismo http://arleytriana.blogspot.com.

        Programación simple y llana

        Utilizando solamente PHP normal y corriente, el script necesario para mostrar los artículos almacenados en una base de datos se muestra a continuación:

        Un script simple

        <?php
        // Conectar con la base de datos y seleccionarla
        $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
        mysql_select_db('blog_db', $conexion);
        // Ejecutar la consulta SQL
        $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);
        ?>
        <html>
        <head>
        <title>Listado de Artículos</title>
        </head>
        <body>
        <h1>Listado de Artículos</h1>
        <table>
        <tr><th>Fecha</th><th>Titulo</th></tr>
        <?php
        // Mostrar los resultados con HTML
        while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
        {
        echo "\t<tr>\n";
        printf("\t\t<td> %s </td>\n", $fila['fecha']);
        printf("\t\t<td> %s </td>\n", $fila['titulo']);
        echo "\t</tr>\n";
        }
        ?>
        </table>
        </body>
        </html>
        <?php
        // Cerrar la conexion
        mysql_close($conexion);
        ?>

        El script anterior es fácil de escribir y rápido de ejecutar, pero muy difícil de mantener y actualizar. Los principales problemas del código anterior son:
        • No existe protección frente a errores (¿qué ocurre si falla la conexión con la base de datos?).
        • El código HTML y el código PHP están mezclados en el mismo archivo e incluso en algunas partes están entrelazados.
        • El código solo funciona si la base de datos es MySQL.

        Separando la presentación

        Las llamadas a echo y printf del listado anterior dificultan la lectura del código. De hecho, modificar el código HTML del script anterior para mejorar la presentación es un muy complicado debido a cómo está programado. Así que el código va a ser dividido en dos partes. En primer lugar, el código PHP puro con toda la lógica de negocio se incluye en el script del controlador, como se muestra a continuación.

        La parte del controlador, en index.php

        <?php
        // Conectar con la base de datos y seleccionarla
        $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
        mysql_select_db('blog_db', $conexion);
        // Ejecutar la consulta SQL
        $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);
        // Crear el array de elementos para la capa de la vista
        $articulos = array();
        while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
        {
        $articulos[] = $fila;
        }
        // Cerrar la conexión
        mysql_close($conexion);
        // Incluir la lógica de la vista
        require('vista.php');
        ?>


        El código HTML, que contiene cierto código PHP a modo de plantilla, se almacena en el script de la vista, como se muestra a continuación.

        La parte de la vista, en vista.php

        <html>
        <head>
        <title>Listado de Artículos</title>
        </head>
        <body>
        <h1>Listado de Artículos</h1>
        <table>
        <tr><th>Fecha</th><th>Título</th></tr>
        <?php foreach ($articulos as $articulo): ?>
        <tr>
        <td><?php echo $articulo['fecha'] ?></td>
        <td><?php echo $articulo['titulo'] ?></td>
        </tr>
        <?php endforeach; ?>
        </table>
        </body>
        </html>


        Una buena regla general para determinar si la parte de la vista está suficientemente limpia de código es que debería contener una cantidad mínima de código PHP, la suficiente como para que un diseñador HTML sin conocimientos de PHP pueda entenderla. Las instrucciones más comunes en la parte de la vista suelen ser echo, if/else, foreach/endforeach y poco más. Además, no se deben incluir instrucciones PHP que generen etiquetas HTML.

        Toda la lógica se ha centralizado en el script del controlador, que solamente contiene código PHP y ningún tipo de HTML. De hecho, y como puedes imaginar, el mismo controlador se puede reutilizar para otros tipos de presentaciones completamente diferentes, como por ejemplo un archivo PDF o una estructura de tipo XML.

        Separando la manipulación de los datos

        La mayor parte del script del controlador se encarga de la manipulación de los datos. Pero, ¿qué ocurre si se necesita la lista de entradas del blog para otro controlador, por ejemplo uno que se dedica a generar el canal RSS de las entradas del blog? ¿Y si se quieren centralizar todas las consultas a la base de datos en un único sitio para evitar duplicidades?

        ¿Qué ocurre si cambia el modelo de datos y la tabla articulo pasa a llamarse articulo_blog? ¿Y si se quiere cambiar a PostgreSQL en vez de MySQL? Para poder hacer todo esto, es imprescindible eliminar del controlador todo el código que se encarga de la manipulación de los datos y ponerlo en otro script, llamado el modelo, tal y como se muestra a continuación.

        La parte del modelo, en modelo.php

        <?php
        function getTodosLosArticulos()
        {
        // Conectar con la base de datos y seleccionarla
        $conexion = mysql_connect('localhost', 'miusuario', 'micontrasena');
        mysql_select_db('blog_db', $conexion);
        // Ejecutar la consulta SQL
        $resultado = mysql_query('SELECT fecha, titulo FROM articulo', $conexion);
        // Crear el array de elementos para la capa de la vista
        $articulos = array();
        while ($fila = mysql_fetch_array($resultado, MYSQL_ASSOC))
        {
        $articulos[] = $fila;
        }
        // Cerrar la conexión
        mysql_close($conexion);
        return $articulos;
        }
        ?>


        El controlador modificado se puede ver aquí.

        La parte del controlador, modificada, en index.php

        <?php
        // Incluir la lógica del modelo
        require_once('modelo.php');
        // Obtener la lista de artículos
        $articulos = getTodosLosArticulos();
        // Incluir la lógica de la vista
        require('vista.php');
        ?>


        Ahora el controlador es mucho más fácil de leer. Su única tarea es la de obtener los datos del modelo y pasárselos a la vista. En las aplicaciones más complejas, el controlador se encarga además de procesar las peticiones, las sesiones de los usuarios, la autenticación, etc. El uso de nombres apropiados para las funciones del modelo hace que sea innecesario añadir comentarios al código del controlador.

        El script del modelo solamente se encarga del acceso a los datos y puede ser reorganizado a tal efecto. Todos los parámetros que no dependen de la capa de datos (como por ejemplo los parámetros de la petición del usuario) se deben obtener a través del controlador y por tanto, no se puede acceder a ellos directamente desde el modelo. Las funciones del modelo se pueden reutilizar fácilmente en otros controladores.

        Separación en capas más allá del MVC

        El principio más importante de la arquitectura MVC es la separación del código del programa en tres capas, dependiendo de su naturaleza. La lógica relacionada con los datos se incluye en el modelo, el código de la presentación en la vista y la lógica de la aplicación en el controlador.

        La programación se puede simplificar si se utilizan otros patrones de diseño. De esta forma, las capas del modelo, la vista y el controlador se pueden subidividir en más capas.

        Abstracción de la base de datos

        La capa del modelo se puede dividir en la capa de acceso a los datos y en la capa de abstracción de la base de datos. De esta forma, las funciones que acceden a los datos no utilizan sentencias ni consultas que dependen de una base de datos, sino que utilizan otras funciones para realizar las consultas. Así, si se cambia de sistema gestor de bases de datos, solamente es necesario actualizar la capa de abstracción de la base de datos.

        La parte del modelo correspondiente a la abstracción de la base de datos: muestra una capa de acceso a datos específica para MySQL.

        <?php
        function crear_conexion($servidor, $usuario, $contrasena)
        {
        return mysql_connect($servidor, $usuario, $contrasena);
        }
        function cerrar_conexion($conexion)
        {
        mysql_close($conexion);
        }
        function consulta_base_de_datos($consulta, $base_datos, $conexion)
        {
        mysql_select_db($base_datos, $conexion);
        return mysql_query($consulta, $conexion);
        }
        function obtener_resultados($resultado)
        {
        return mysql_fetch_array($resultado, MYSQL_ASSOC);
        }
        ?>


        La parte del modelo correspondiente al acceso a los datos: muestra una capa sencilla de abstracción de la base de datos.

        <?php
        function getTodosLosArticulos()
        {
        // Conectar con la base de datos
        $conexion = crear_conexion('localhost', 'miusuario', 'micontrasena');
        // Ejecutar la consulta SQL
        $resultado = consulta_base_de_datos('SELECT fecha, titulo FROM articulo', 'blog_db', $conexion);
        // Crear el array de elementos para la capa de la vista
        $articulos = array();
        while ($fila = obtener_resultados($resultado))
        {
        $articulos[] = $fila;
        }
        // Cerrar la conexión
        cerrar_conexion($conexion);
        return $articulos;
        }
        ?>

        Como se puede comprobar, la capa de acceso a datos no contiene funciones dependientes de ningún sistema gestor de bases de datos, por lo que es independiente de la base de datos utilizada. Además, las funciones creadas en la capa de abstracción de la base de datos se pueden reutilizar en otras funciones del modelo que necesiten acceder a la base de datos.

        NOTA

        Estos últimos dos ejemplos no son completos, y todavía hace falta añadir algo de código para tener una completa abstracción de la base de datos (abstraer el código SQL mediante un constructor de consultas independiente de la base de datos, añadir todas las funciones a una clase, etc.) El propósito de este artículo no es mostrar cómo se puede escribir todo ese código.

        Los elementos de la vista

        La capa de la vista también puede aprovechar la separación de código. Las páginas web suelen contener elementos que se muestran de forma idéntica a lo largo de toda la aplicación: cabeceras de la página, el layout genérico, el pie de página y la navegación global. Normalmente sólo cambia el interior de la página. Por este motivo, la vista se separa en un layout y en una plantilla. Normalmente, el layout es global en toda la aplicación o al menos en un grupo de páginas. La plantilla sólo se encarga de visualizar las variables definidas en el controlador. Para que estos componentes interaccionen entre sí correctamente, es necesario añadir cierto código. Siguiendo estos principios, la parte de la vista del scrip inicial se puede separar en tres partes.

        La parte de la plantilla de la vista, en miplantilla.php

        <h1>Listado de Artículos</h1>
        <table>
        <tr><th>Fecha</th><th>Título</th></tr>
        <?php foreach ($articulos as $articulo): ?>
        <tr>
        <td><?php echo $articulo['fecha'] ?></td>
        <td><?php echo $articulo['titulo'] ?></td>
        </tr>
        <?php endforeach; ?>
        </table>


        La parte de la lógica de la vista

        <?php
        $titulo = 'Listado de Artículos';
        $contenido = include('miplantilla.php');
        ?>


        La parte del layout de la vista

        <html>
        <head>
        <title><?php echo $titulo ?></title>
        </head>
        <body>
        <?php echo $contenido ?>
        </body>
        </html>


        Acciones y controlador frontal

        En el ejemplo anterior, el controlador no se encargaba de realizar muchas tareas, pero en las aplicaciones web reales el controlador suele tener mucho trabajo. Una parte importante de su trabajo es común a todos los controladores de la aplicación. Entre las tareas comunes se encuentran el manejo de las peticiones del usuario, el manejo de la seguridad, cargar la configuración de la aplicación y otras tareas similares. Por este motivo, el controlador normalmente se divide en un controlador frontal, que es único para cada aplicación, y las acciones, que incluyen el código específico del controlador de cada página.

        Una de las principales ventajas de utilizar un controlador frontal es que ofrece un punto de entrada único para toda la aplicación. Así, en caso de que sea necesario impedir el acceso a la aplicación, solamente es necesario editar el script correspondiente al controlador frontal. Si la aplicación no dispone de controlador frontal, se debería modificar cada uno de los controladores.

        Orientación a objetos

        Los ejemplos anteriores utilizan la programación procedimental. Las posibilidades que ofrecen los lenguajes de programación modernos para trabajar con objetos permiten simplificar la programación, ya que los objetos pueden encapsular la lógica, heredar métodos y atributos entre diferentes objetos y proporcionan una serie de convenciones claras sobre la forma de nombrar a los objetos.

        La implementación de una arquitectura MVC en un lenguaje de programación que no está orientado a objetos puede encontrarse con problemas de namespaces y código duplicado, dificultando la lectura del código de la aplicación.

        La orientación a objetos permite a los desarrolladores trabajar con objetos de la vista, objetos del controlador y clases del modelo, transformando las funciones de los ejemplos anteriores en métodos. Se trata de un requisito obligatorio para las arquitecturas de tipo MVC.

        Otros artículos sobre Patrones
        1. Patron Singleton en PHP
        2. Patrón de diseño Decorator en PHP
        3. Patrón Simple Factory en PHP
        4. Patrón Registry en PHP

        Sugerencias

        Si quieres profundizar en el tema de los patrones de diseño para las aplicaciones web en el contexto de la orientación a objetos, puedes leer “Patterns of Enterprise Application Architecture” de Martin Fowler (Addison-Wesley, ISBN: 0-32112-742-0). El código de ejemplo del libro de Fowler está escrito en Java y en C#, pero es bastante fácil de leer para los programadores de PHP.

        6 comentarios:

        1. Muy buen documento, me ha ayudado mucho

          ResponderEliminar
        2. Excelente explicación, sencilla y entendible, muchisimas gracias.

          ResponderEliminar
        3. Muy buena explicacion, graciaas.
          Tendras algun ejemplo aplicando POO

          ResponderEliminar
        4. Saludos.

          Muy bueno te felicito y tengo una pregunta. Puedo comenzar a programar con un framework sin tener la experiencia php. Quiero decir solo tengo nociones de php y experiencia en programacion en otros lenguajes procedimentales.

          Gracias.

          ResponderEliminar
        5. Hola Jose,

          No te lo recomiendo, primero sienta las bases.

          Saludos

          ResponderEliminar