Ver índice de contenidos del libro

2.1. El patrón MVC

Symfony está basado en un patrón clásico del diseño web conocido como arquitectura MVC, que está formado por tres niveles:

  • El Modelo representa la información con la que trabaja la aplicación, es decir, su lógica de negocio.
  • La Vista transforma el modelo en una página web que permite al usuario interactuar con ella.
  • El Controlador se encarga de procesar las interacciones del usuario y realiza los cambios apropiados en el modelo o en la vista.

La Figura 2-1 ilustra el funcionamiento del patrón MVC.

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 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.

El patrón MVC

Figura 2.1 El patrón MVC

2.1.1. 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.

2.1.1.1. Programación simple

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

Listado 2-1 - 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.

2.1.1.2. Separando la presentación

Las llamadas a echo y printf del listado 2-1 dificultan la lectura del código. De hecho, modificar el código HTML del script anterior para mejorar la presentación es un follón 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 en el listado 2-2.

Listado 2-2 - 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 en el listado 2-3.

Listado 2-3 - 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/endif, 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.

2.1.1.3. 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 en el listado 2-4.

Listado 2-4 - 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 en el listado 2-5.

Listado 2-5 - 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 hacen 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.

2.1.2. 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.

2.1.2.1. 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.

El listado 2-6 muestra un ejemplo de capa de abstracción de la base de datos y el listado 2-7 muestra una capa de acceso a datos específica para MySQL.

Listado 2-6 - La parte del modelo correspondiente a la abstracción de la base de datos

<?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);
}

Listado 2-7 - La parte del modelo correspondiente al acceso a los datos

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 Los ejemplos de los listados 2-6 y 2-7 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 libro no es mostrar cómo se puede escribir todo ese código, ya que en el capítulo 8 se muestra cómo Symfony realiza de forma automática toda la abstracción.

2.1.2.2. 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 listado 2-3 se puede separar en tres partes, como se muestra en los listados 2-8, 2-9 y 2-10.

Listado 2-8 - 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>

Listado 2-9 - La parte de la lógica de la vista

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

Listado 2-10 - La parte del layout de la vista

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

2.1.2.3. 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.

2.1.2.4. 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, pueden 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.

Truco 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.

2.1.3. La implementación del MVC que realiza Symfony

Piensa por un momento cuántos componentes se necesitarían para realizar una página sencilla que muestre un listado de las entradas o artículos de un blog. Como se muestra en la figura 2-2, son necesarios los siguientes componentes:

  • La capa del Modelo
    • Abstracción de la base de datos
    • Acceso a los datos
  • La capa de la Vista
    • Vista
    • Plantilla
    • Layout
  • La capa del Controlador
    • Controlador frontal
    • Acción

En total son siete scripts, lo que parecen muchos archivos para abrir y modificar cada vez que se crea una página. Afortunadamente, Symfony simplifica este proceso. Symfony toma lo mejor de la arquitectura MVC y la implementa de forma que el desarrollo de aplicaciones sea rápido y sencillo.

En primer lugar, el controlador frontal y el layout son comunes para todas las acciones de la aplicación. Se pueden tener varios controladores y varios layouts, pero solamente es obligatorio tener uno de cada. El controlador frontal es un componente que sólo tiene código relativo al MVC, por lo que no es necesario crear uno, ya que Symfony lo genera de forma automática.

La otra buena noticia es que las clases de la capa del modelo también se generan automáticamente, en función de la estructura de datos de la aplicación. La librería Propel se encarga de esta generación automática, ya que crea el esqueleto o estructura básica de las clases y genera automáticamente el código necesario. Cuando Propel encuentra restricciones de claves foráneas (o externas) o cuando encuentra datos de tipo fecha, crea métodos especiales para acceder y modificar esos datos, por lo que la manipulación de datos se convierte en un juego de niños. La abstracción de la base de datos es completamente invisible al programador, ya que la realiza otro componente específico llamado Creole. Así, si se cambia el sistema gestor de bases de datos en cualquier momento, no se debe reescribir ni una línea de código, ya que tan sólo es necesario modificar un parámetro en un archivo de configuración.

Por último, la lógica de la vista se puede transformar en un archivo de configuración sencillo, sin necesidad de programarla.

El flujo de trabajo de Symfony

Figura 2.2 El flujo de trabajo de Symfony

Considerando todo lo anterior, el ejemplo de la página que muestra un listado con todas las entradas del blog solamente requiere de tres archivos en Symfony, que se muestran en los listados 2-11, 2-12 y 2-13.

Listado 2-11 - Acción listado, en miproyecto/apps/miaplicacion/modules/weblog/actions/actions.class.php

<?php
 
class weblogActions extends sfActions
{
  public function executeListado()
  {
    $this->articulos = ArticuloPeer::doSelect(new Criteria());
  }
}

Listado 2-12 - Plantilla listado, en miproyecto/apps/miaplicacion/modules/weblog/templates/listadoSuccess.php

<?php slot('titulo', 'Listado de Artículos') ?>
 
<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->getFecha() ?></td>
    <td><?php echo $articulo->getTitulo() ?></td>
  </tr>
<?php endforeach; ?>
</table>

Además es necesario crear un layout como el del listado 2-13. Afortunadamente, el mismo layout se puede reutilizar muchas veces.

Listado 2-13 - Layout, en miproyecto/apps/miaplicacion/templates/layout.php

<html>
  <head>
    <title><?php include_slot('titulo') ?></title>
  </head>
  <body>
    <?php echo $sf_content ?>
  </body>
</html>

Estos scripts son todo lo que necesita la aplicación del ejemplo. El código mostrado es el necesario para crear la misma página que generaba el script simple del listado 2-1. Symfony se encarga del resto de tareas, como hacer que los componentes interactuen entre sí. Si se considera el número de líneas de código, el listado de entradas de blog creado según la arquitectura MVC no requiere más líneas ni más tiempo de programación que un script simple. Sin embargo, la arquitectura MVC proporciona grandes ventajas, como la organización del código, la reutilización, la flexibilidad y una programación mucho más entretenida. Por si fuera poco, crear la aplicación con Symfony permite crear páginas XHTML válidas, depurar fácilmente las aplicaciones, crear una configuración sencilla, abstracción de la base de datos utilizada, enrutamiento con URL limpias, varios entornos de desarrollo y muchas otras utilidades para el desarrollo de aplicaciones.

2.1.4. Las clases que forman el núcleo de Symfony

La implementación que realiza Symfony de la arquitectura MVC incluye varias clases que se mencionan una y otra vez a lo largo del libro:

  • sfController es la clase del controlador. Se encarga de decodificar la petición y transferirla a la acción correspondiente.
  • sfRequest almacena todos los elementos que forman la petición (parámetros, cookies, cabeceras, etc.)
  • sfResponse contiene las cabeceras de la respuesta y los contenidos. El contenido de este objeto se transforma en la respuesta HTML que se envía al usuario.
  • El contexto (que se obtiene mediante sfContext::getInstance()) almacena una referencia a todos los objetos que forman el núcleo de Symfony y puede ser accedido desde cualquier punto de la aplicación.

El capítulo 6 explica en detalle todos estos objetos.

Como se ha visto, todas las clases de Symfony utilizan el prefijo sf, como también hacen todas las variables principales de Symfony en las plantillas. De esta forma, se evitan las colisiones en los nombres de clases y variables de Symfony y los nombres de tus propias clases y variables, además de que las clases del framework son más fáciles de reconocer.

Nota Entre las normas seguidas por el código de Symfony, se encuentra el estándar "UpperCamelCase" para el nombre de las clases y variables. Solamente existen dos excepciones: las clases del núcleo de Symfony empiezan por sf (por tanto en minúsculas) y las variables utilizadas en las plantillas que utilizan la sintaxis de separar las palabras con guiones bajos.

Nota del traductor La notación "CamelCase" consiste en escribir frases o palabras compuestas eliminando los espacios intermedios y poniendo en mayúscula la primera letra de cada palabra. La variante "UpperCamelCase" también pone en mayúscula la primera letra de todas.