Symfony 2.4, el libro oficial

2.3. El controlador frontal al rescate

Una solución mucho mejor es usar un controlador frontal: un único archivo PHP a través del cual se procesen todas las peticiones del usuario. Con un controlador frontal, la URI de la aplicación cambia un poco, pero se vuelve mucho más flexible:

Sin controlador frontal:
/index.php       => (ejecuta index.php) la página que lista los artículos.
/show.php        => (ejecuta show.php)  la página que muestra un artículo específico.

Con index.php como controlador frontal
/index.php       => (ejecuta index.php) la página que lista los artículos.
/index.php/show  => (ejecuta index.php) la página que muestra un artículo específico.

Truco El nombre del archivo index.php se puede eliminar en las URLs si utilizas las reglas de reescritura del servidor web Apache (o las equivalentes de los demás servidores web). En ese caso, la URI resultante de la página show del blog sería simplemente /show.

Cuando se usa un controlador frontal, un solo archivo PHP (index.php en este caso) procesa todas las peticiones. Para la página show del blog, /index.php/show realmente ejecuta el archivo index.php, que ahora es el responsable de dirigir internamente las peticiones basándose en la URI completa. Como puedes ver, un controlador frontal es una herramienta muy poderosa.

2.3.1. Creando el controlador frontal

Estás a punto de dar un gran paso en la aplicación. Con un archivo manejando todas las peticiones, puedes centralizar cosas como el manejo de la seguridad, la carga de la configuración y el enrutamiento. En nuestra aplicación, index.php ahora debe ser lo suficientemente inteligente como para mostrar la lista de entradas del blog o mostrar la página de una entrada particular basándose en la URI solicitada:

<?php
// index.php

// carga e inicia algunas librerías globales
require_once 'model.php';
require_once 'controllers.php';

// encamina la petición internamente
$uri = $_SERVER['REQUEST_URI'];
if ($uri == '/index.php') {
    list_action();
} elseif ($uri == '/index.php/show' && isset($_GET['id'])) {
    show_action($_GET['id']);
} else {
    header('Status: 404 Not Found');
    echo '<html><body><h1>Page Not Found</h1></body></html>';
}

Para organizar mejor el código, los dos controladores anteriores (archivos index.php y show.php) se han convertido en funciones PHP del nuevo archivo controllers.php:

function list_action()
{
    $posts = get_all_posts();
    require 'templates/list.php';
}

function show_action($id)
{
    $post = get_post_by_id($id);
    require 'templates/show.php';
}

Como controlador frontal, index.php ha asumido un papel completamente nuevo, que incluye la carga de los archivos principales de la aplicación (model.php y controllers.php) y la decisión de ejecutar uno de los dos controladores (las funciones list_action() y show_action()). En realidad, el controlador frontal está empezando a parecerse y actuar como el mecanismo que define Symfony2 para la manipulación y enrutado de peticiones.

Truco Otra ventaja del controlador frontal es la flexibilidad de las URL. Ten en cuenta que la URL de la página show del blog se puede cambiar de /show a /read cambiando el código en un único lugar. Antes, era necesario cambiar todo un archivo para cambiar el nombre. En Symfony2, las URL son incluso más flexibles.

Por ahora, la aplicación ha evolucionado de un único archivo PHP, a una estructura organizada que permite la reutilización de código. Debes estar contento, pero aún lejos de estar satisfecho. Por ejemplo, el sistema de enrutamiento es muy mejorable, ya que no reconoce por ejemplo que la página list (/index.php) también debe ser accesible a través de / (si has agregado las reglas de reescritura de Apache). Además, en lugar de programar el blog, has perdido tu tiempo en preparar la "arquitectura" del código (por ejemplo, el enrutamiento, los controladores, las plantillas, etc.) Y pronto tendrás que perder más tiempo en la gestión de los formularios, la validación de la información, crear un archivo de log, la gestión de la seguridad, etc.

¿Por qué tienes que dedicarte a solucionar estos problemas que se repiten una y otra vez en todos los proyectos?

2.3.2. Añadiendo un toque de Symfony2

¡Symfony2 al rescate! Antes de utilizar Symfony2, debes descargar sus archivos. Para ello se utiliza la herramienta llamada Composer, que se encarga de descargar la versión correcta de los archivos de Symfony, todas sus dependencias y también proporciona un cargador automático de clases (llamado class loader en inglés). Un cargador automático es una herramienta que permite empezar a utilizar clases PHP sin incluir explícitamente el archivo que contiene la clase.

Dentro del directorio raíz del proyecto crea un archivo llamado composer.json con el siguiente contenido:

{
    "require": {
        "symfony/symfony": "2.4.*"
    },
    "autoload": {
        "files": ["model.php","controllers.php"]
    }
}

A continuación, descarga Composer y ejecuta después el siguiente código para descargar Symfony dentro del directorio vendor/ del proyecto:

$ php composer.phar install

Además de descargar todas las dependencias, Composer genera un archivo llamado vendor/autoload.php, que se encarga de cargar automáticamente todas las clases del framework Symfony y de todas las librerías que añadas en la sección autoload del archivo composer.json.

La esencia de la filosofía Symfony es que el trabajo principal de una aplicación es interpretar cada petición y devolver una respuesta. Con este fin, Symfony2 proporciona las clases Request y Response. Estas clases son representaciones orientadas a objetos de la petición HTTP que se está procesando y la respuesta HTTP que se devolverá. Úsalas para mejorar el blog:

<?php
// index.php
require_once 'vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$uri = $request->getPathInfo();
if ($uri == '/') {
    $response = list_action();
} elseif ($uri == '/show' && $request->query->has('id')) {
    $response = show_action($request->query->get('id'));
} else {
    $html = '<html><body><h1>Page Not Found</h1></body></html>';
    $response = new Response($html, Response::HTTP_NOT_FOUND);
}

// envía las cabeceras y la respuesta
$response->send();

Los controladores se encargan de devolver un objeto Response. Para simplificar la generación del código HTML de la respuesta, crea una nueva función llamada render_template(), la cual, por cierto, actúa un poco como el motor de plantillas de Symfony2:

// controllers.php
use Symfony\Component\HttpFoundation\Response;

function list_action()
{
    $posts = get_all_posts();
    $html = render_template('templates/list.php', array('posts' => $posts));

    return new Response($html);
}

function show_action($id)
{
    $post = get_post_by_id($id);
    $html = render_template('templates/show.php', array('post' => $post));

    return new Response($html);
}

// función ayudante para reproducir plantillas
function render_template($path, array $args)
{
    extract($args);
    ob_start();
    require $path;
    $html = ob_get_clean();

    return $html;
}

Aunque solo ha añadido una pequeñísima parte de las ideas de Symfony2, la aplicación ahora es más flexible y fiable. El objeto Request proporciona una manera segura para acceder a la información de la petición HTTP. El método getPathInfo() por ejemplo devuelve una URI limpia (siempre devolviendo /show y nunca /index.php/show).

Por lo tanto, incluso si el usuario va a /index.php/show, la aplicación es lo suficientemente inteligente para encaminar la petición hacia show_action().

El objeto Response proporciona flexibilidad al construir la respuesta HTTP, permitiendo que las cabeceras HTTP y el contenido se definan a través de una interfaz orientada a objetos.

Y aunque las respuestas en esta aplicación son simples, esta flexibilidad será muy útil en cuanto tu aplicación crezca.

2.3.3. Aplicación de ejemplo en Symfony2

El blog ha mejorado bastante, pero todavía contiene una gran cantidad de código para ser una aplicación tan simple. Durante las mejoras de la aplicación, se ha creado un sistema de enrutamiento muy sencillo y otro método que utiliza ob_start() y ob_get_clean() para procesar plantillas. Si vas a continuar desarrollando este blog, lo mejor es que utilices los componentes Routing y Templating de Symfony para mejorar esa parte sin esfuerzo.

En lugar de resolver de nuevo los mismos problemas de siempre, deja que Symfony2 se encargue de ellos por ti. Aquí está la misma aplicación de ejemplo, pero construida ahora con Symfony2:

<?php
// src/Acme/BlogBundle/Controller/BlogController.php

namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function listAction()
    {
        $posts = $this->get('doctrine')
            ->getManager()
            ->createQuery('SELECT p FROM AcmeBlogBundle:Post p')
            ->execute();

        return $this->render('AcmeBlogBundle:Blog:list.html.php', array('posts' => $posts));
    }

    public function showAction($id)
    {
        $post = $this->get('doctrine')
            ->getManager()
            ->getRepository('AcmeBlogBundle:Post')
            ->find($id);

        if (!$post) {
            // hace que se muestre la página de error 404
            throw $this->createNotFoundException();
        }

        return $this->render('AcmeBlogBundle:Blog:show.html.php', array('post' => $post));
    }
}

Los dos controladores siguen siendo más o menos pequeños. Cada uno utiliza la librería ORM de Doctrine para recuperar objetos de la base de datos y el componente Templating para generar el código HTML con una plantilla y devolver un objeto Response. La plantilla list ahora es un poco más simple:

<!-- src/Acme/BlogBundle/Resources/views/Blog/lista.html.php -->
<?php $view->extend('::base.html.php') ?>

<?php $view['slots']->set('title', 'List of Posts') ?>

<h1>List of Posts</h1>
<ul>
    <?php foreach ($posts as $post): ?>
    <li>
        <a href="<?php echo $view['router']->generate('blog_show', array('id' => $post->getId())) ?>">
            <?php echo $post->getTitle() ?>
            </a>
    </li>
    <?php endforeach; ?>
</ul>

Y el diseño de la plantilla base también es muy parecido al de antes:

<!-- app/Resources/views/base.html.php -->
<html>
    <head>
    <title><?php echo $view['slots']->output('title', 'Default title') ?></title>
    </head>
    <body>
    <?php echo $view['slots']->output('_content') ?>
    </body>
</html>

Nota Te vamos a dejar como ejercicio la plantilla show, porque debería ser trivial crearla basándote en la plantilla list.

Cuando arranca el motor de Symfony2 (llamado kernel), necesita un mapa para saber qué controladores ejecutar en base a la información solicitada por los usuarios. Este mapa se crea con la configuración de enrutamiento, que proporciona esta información en formato legible:

# app/config/routing.yml
blog_list:
    path:     /blog
    defaults: { _controller: AcmeBlogBundle:Blog:list }

blog_show:
    path:     /blog/show/{id}
    defaults: { _controller: AcmeBlogBundle:Blog:show }

Ahora que Symfony2 se encarga de todas las tareas repetitivas, el controlador frontal es muy simple. Y ya que hace tan poco, nunca tienes que volver a tocarlo una vez creado (y si utilizas una distribución Symfony2, ¡ni siquiera tendrás que crearlo!):

<?php
// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();

El único trabajo del controlador frontal es iniciar el motor de Symfony2 (Kernel) y pasarle un objeto Request para que lo manipule. Después, el núcleo de Symfony2 utiliza la información de enrutamiento para determinar qué controlador se ejecuta. Al igual que antes, el método controlador es el responsable de devolver el objeto Response final. Y eso es todo lo que hace el controlador frontal.

2.3.4. Qué más ofrece Symfony2

En los siguientes capítulos, aprenderás más acerca de cómo funciona cada parte de Symfony y la organización recomendada de un proyecto. Ahora vamos a ver cómo nos ha facilitado nuestro trabajo de programadores el migrar el blog de PHP simple a Symfony2:

  • Tu aplicación cuenta con código claro y bien organizado (aunque Symfony no te obliga a ello). Esto facilita la reutilización de código y permite a los nuevos desarrolladores ser productivos en el proyecto con mayor rapidez.
  • El 100% del código que escribes es para tu aplicación. No necesitas desarrollar o mantener herramientas de bajo nivel como la carga automática de clases, el enrutamiento o los controladores.
  • Symfony2 te proporciona acceso a herramientas de software libre tales como Doctrine, plantillas, seguridad, formularios, validación y traducción (por nombrar algunas).
  • La aplicación ahora dispone de URLs totalmente flexibles gracias al componente Routing.
  • La arquitectura centrada en HTTP de Symfony2 te da acceso a herramientas muy potentes, como por ejemplo la caché HTTP (mediante la caché HTTP interna de Symfony2 o mediante herramientas más avanzadas como Varnish). Esto se explica más adelante en el capítulo sobre la caché.

Y lo mejor de todo, utilizando Symfony2, ¡ahora tienes acceso a un conjunto de herramientas de software libre de alta calidad desarrolladas por la comunidad Symfony2!

Puedes encontrar una buena colección de herramientas comunitarias de Symfony2 en KnpBundles.com.