Symfony 2.3, el libro oficial

4.2. Antes de empezar, crea el bundle

Antes de empezar, tendrás que crear un bundle (que se podría traducir como "paquete"). En Symfony2, un bundle es como un plugin, con la salvedad de que todo el código de tu aplicación se almacena dentro de bundles.

Un bundle no es más que un directorio que almacena todo lo relacionado con una función específica, incluyendo clases PHP, configuración, e incluso hojas de estilo y archivos de Javascript.

Para crear un bundle llamado AcmeHelloBundle (el bundle de ejemplo que vamos a construir en este capítulo), ejecuta el siguiente comando y sigue las instrucciones en pantalla (acepta todas las opciones predeterminadas):

$ php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml

Sin que te des cuenta, se ha creado un directorio para el bundle en src/Acme/HelloBundle. Además se ha añadido automáticamente una línea en el archivo app/AppKernel.php para registrar el bundle en el núcleo de Symfony:

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        ...,
        new Acme\HelloBundle\AcmeHelloBundle(),
    );
    // ...

    return $bundles;
}

Ahora que ya está configurado el bundle, puedes comenzar a construir tu aplicación dentro del bundle.

4.2.1. Paso 1: Creando la ruta

Por defecto, el archivo de configuración de enrutamiento en una aplicación Symfony2 se encuentra en app/config/routing.yml. Si lo prefieres, y al igual que en el resto de la configuración en Symfony2, puedes utilizar el formato XML o PHP para configurar tus rutas.

Si te fijas en el archivo de enrutamiento principal, verás que Symfony ya ha agregado una entrada al generar el bundle AcmeHelloBundle:

# app/config/routing.yml
acme_hello:
    resource: "@AcmeHelloBundle/Resources/config/routing.yml"
    prefix:   /
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <import resource="@AcmeHelloBundle/Resources/config/routing.xml"
        prefix="/" />
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$collection = new RouteCollection();
$collection->addCollection(
    $loader->import('@AcmeHelloBundle/Resources/config/routing.php'),
    '/'
);

return $collection;

Esta directiva de configuración le dice a Symfony que cargue la configuración de enrutamiento del archivo Resources/config/routing.yml que se encuentra en el interior del bundle AcmeHelloBundle.

En otras palabras, puedes configurar tus rutas directamente en el archivo app/config/routing.yml o puedes definirlas en varias partes de la aplicación y después las importas desde ese archivo.

Ahora que el archivo routing.yml del bundle se importa desde el archivo de enrutamiento principal de la aplicación, añade la nueva ruta que define la URL de la página que estás a punto de crear:

# src/Acme/HelloBundle/Resources/config/routing.yml
hello:
    path:     /hello/{name}
    defaults: { _controller: AcmeHelloBundle:Hello:index }
<!-- src/Acme/HelloBundle/Resources/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="hello" path="/hello/{name}">
        <default key="_controller">AcmeHelloBundle:Hello:index</default>
    </route>
</routes>
// src/Acme/HelloBundle/Resources/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$collection = new RouteCollection();
$collection->add('hello', new Route('/hello/{name}', array(
    '_controller' => 'AcmeHelloBundle:Hello:index',
)));

return $collection;

La ruta se compone básicamente de dos partes: el path, que es la URL con la que debe coincidir la petición del usuario para activar la ruta, y un array llamado defaults, que especifica el controlador que se ejecuta. Las partes de la URL encerradas entre comillas indican que su valor puede variar. De esta forma, {name} significa que las URL /hello/Ryan, /hello/Fabien o cualquier otra URI similar coincidirá con esta ruta. El valor de las partes variables también se pasa al controlador, que puede acceder a ellos a través del nombre asignado en la propia ruta (name en este caso).

Nota El sistema de enrutamiento tiene muchas más características para crear URI flexibles y avanzadas en tu aplicación. Para más detalles, consulta el capítulo de enrutamiento.

4.2.2. Paso 2: Creando el controlador

Cuando el usuario solicita la URL /hello/Ryan, se activa la ruta hello, a la que corresponde el controlador AcmeHelloBundle:Hello:index, que es realmente el código que se ejecuta. El segundo paso del proceso de creación de páginas consiste precisamente en crear ese controlador.

La cadena AcmeHelloBundle:Hello:index es el nombre lógico del controlador, que se traduce como el método indexAction() de una clase PHP llamada Acme\HelloBundle\Controller\Hello. Crea en primer lugar este archivo dentro de tu bundle AcmeHelloBundle:

// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
}

En realidad, el controlador no es más que un método PHP que tú creas y Symfony ejecuta. Aquí es donde el código utiliza la información de la petición para construir y preparar el recurso solicitado. Salvo en algunos casos avanzados, el resultado final de un controlador siempre es el mismo: un objeto Response de Symfony2.

Crea el método indexAction que Symfony ejecutará cuando se sirva la ruta hello:

// src/Acme/HelloBundle/Controller/HelloController.php

// ...
class HelloController
{
    public function indexAction($name)
    {
        return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
}

El controlador es muy sencillo: crea un nuevo objeto de tipo Response y cuyo primer argumento es el contenido que se utiliza para crear la respuesta enviada al usuario (en este caso, una página HTML muy simple).

¡Enhorabuena! Después de crear solamente una ruta y un controlador ¡ya tienes una página completamente funcional! Si todo lo has configurado correctamente, la aplicación debe darte la bienvenida al acceder a la siguiente URL:

http://localhost/app_dev.php/hello/Ryan

Truco También puedes ver tu aplicación en el entorno prod o de producción visitando:

http://localhost/app.php/hello/Ryan

Si se produce un error, probablemente sea porque necesitas vaciar la caché de producción ejecutando el siguiente comando:

php app/console cache:clear --env=prod --no-debug

El tercer y último paso para crear una página es opcional pero casi todas las aplicaciones lo hacen: crear una plantilla.

Nota Los controladores son el punto de entrada principal a tu código y la clave en la creación de páginas. Puedes encontrar mucha más información en el capítulo dedicado a los controladores.

4.2.3. Paso 3 opcional: Creando la plantilla

Las plantillas te permiten mover toda la parte de la vista (es decir, el código HTML) a un archivo separado y reutilizar diferentes partes del diseño de la página. En vez de escribir el código HTML dentro del controlador, genera el código HTML a partir de una plantilla:

// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class HelloController extends Controller
{
    public function indexAction($name)
    {
        return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));

        // si utilizas PHP en vez de Twig
        // return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => $name));
    }
}

Nota Para poder usar el método render(), tu controlador debe extender de la clase Symfony\Bundle\FrameworkBundle\Controller\Controller, la cual añade atajos para las tareas más comunes de los controladores. Esto se hace en el ejemplo anterior añadiendo la declaración use debajo del namespace y luego añadiendo el extends Controller a la clase.

El método render() crea un objeto Response y le añade el contenido resultante de renderizar la plantilla. Así que como cualquier otro controlador, el código anterior realmente está devolviendo un objeto de tipo Response.

Ten en cuenta que puedes procesar las plantillas de dos formas diferentes. Por defecto Symfony2 admite dos lenguajes de plantillas: las clásicas plantillas creadas con PHP y las nuevas y concisas plantillas creadas con Twig. No te asustes porque puedes elegir libremente cuál utilizar o incluso mezclar las dos en el mismo proyecto.

Al procesar la plantilla AcmeHelloBundle:Hello:index.html.twig, el controlador utiliza la siguiente convención de nomenclatura:

NombreBundle:NombreControlador:NombrePlantilla

Este es el nombre lógico de la plantilla, que se traduce a un archivo físico utilizando la siguiente convención:

</ruta/a/Nombrebundle>/Resources/views/<NombreControlador>/<NombrePlantilla>

En este caso, AcmeHelloBundle es el nombre del bundle, Hello es el controlador e index.html.twig la plantilla:

{# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #}
{% extends '::base.html.twig' %}

{% block body %}
    Hello {{ name }}!
{% endblock %}
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend('::base.html.php') ?>

Hello <?php echo $view->escape($name) ?>!

Veamos la situación a través de la plantilla Twig línea por línea:

  • La etiqueta extends indica que se utiliza una plantilla padre donde se define el diseño del sitio web.
  • La etiqueta block indica que todo su contenido se debe colocar dentro de un bloque llamado body. Como se explicará más adelante, la plantilla padre (base.html.twig) es la responsable de definir ese bloque y de mostrarlo en la página HTML adecuadamente.

La plantilla padre se indica como ::base.html.twig, por lo que no incluye ni la parte del nombre del bundle ni la del nombre del controlador (de ahí los dos puntos dobles (::) al principio). Esto significa que la plantilla no se encuentra dentro de ningún bundle, sino en el directorio app del proyecto:

{# app/Resources/views/base.html.twig #}
<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}{% endblock %}
    <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
    {% block body %}{% endblock %}
    {% block javascripts %}{% endblock %}
    </body>
</html>
<!-- app/Resources/views/base.html.php -->
<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title><?php $view['slots']->output('title', 'Welcome!') ?></title>
    <?php $view['slots']->output('stylesheets') ?>
    <link rel="shortcut icon" href="<?php echo $view['assets']->getUrl('favicon.ico') ?>" />
    </head>
    <body>
    <?php $view['slots']->output('_content') ?>
    <?php $view['slots']->output('stylesheets') ?>
    </body>
</html>

El archivo de la plantilla base define el diseño HTML y muestra el contenido del bloque body que se definió en la plantilla index.html.twig. También muestra el contenido de un bloque llamado title, que si quieres puedes definir en la plantilla index.html.twig. Como no has definido ese bloque en la plantilla, se utiliza el valor predeterminado "Welcome!".

Las plantillas son realmente útiles para generar y organizar el contenido de las páginas de la aplicación. Una plantilla puede generar cualquier cosa, desde el código HTML o CSS hasta cualquier otra cosa que el controlador tenga que devolver.

Dentro del ciclo petición/respuesta, el motor de plantillas simplemente es una herramienta opcional. Recuerda que el objetivo de cada controlador es devolver un objeto Response. Así que por muy potentes que sean, las plantillas son solo una forma opcional de generar ese objeto Response.