HTTP Enhorabuena! Aprender a programar con Symfony2 es una de las mejores formas de convertirse en un programador web ms productivo, completo y popular (bueno, esto ltimo en realidad depende de t). Symfony2 est diseado para volver a lo bsico: herramientas de desarrollo que te permiten programar ms rpido y construir aplicaciones ms robustas, y que afectan lo mnimo a tu forma de trabajar. Symfony est basado en las mejores ideas de muchas tecnologas: las herramientas y conceptos que ests a punto de aprender representan el esfuerzo de miles de personas, durante muchos aos. En otras palabras, no ests aprendiendo "Symfony", ests aprendiendo los fundamentos de la web, buenas prcticas de desarrollo, y cmo utilizar algunas de las mejores libreras PHP publicadas recientemente. Por lo tanto, preprate! Fiel a la filosofa Symfony2, este captulo comienza explicando el concepto fundamental comn para el desarrollo web: HTTP. No importa cul sea tu experiencia previa o cules sean tus lenguajes de programacin favoritos, este captulo es una lectura obligada para todo el mundo. 1.1. HTTP es simple HTTP ("HyperText Transfer Protocol") es un lenguaje basado en texto que permite a dos mquinas comunicarse entre s. Eso es todo! La siguiente conversacin es la que por ejemplo tiene lugar cuando quieres acceder a la ltima tira cmica publicada por el sitio xkcd:
Figura 1.1 Flujo HTTP para obtener la tira cmica ms reciente de Xkcd Y aunque el lenguaje realmente utilizado es un poco ms formal, sigue siendo bastante simple. HTTP es el trmino utilizado para describir este lenguaje simple basado en texto. Y no importa cmo desarrolles en la web, el objetivo de tu servidor siempre es entender las peticiones de texto simple, y devolver respuestas en texto simple. Symfony2 est diseado en base a esa realidad. Aunque a veces no te des cuenta, HTTP es algo que usas todos los das. Con Symfony2, aprenders a dominarlo. 1.1.1. Paso 1: El cliente enva una peticin Todas las conversaciones en la web comienzan con una peticin. La peticin es un mensaje de texto creado por un cliente (por ejemplo un navegador, una aplicacin para el iPhone, etc.) en un formato especial conocido como HTTP. El cliente enva la peticin a un servidor, y luego espera la respuesta. Echa un vistazo a la primera parte de la interaccin (la peticin) entre un navegador y el servidor web del sitio xkcd:
Figura 1.2 Peticin HTTP para obtener la tira cmica ms reciente de Xkcd Utilizando el lenguaje HTTP, esta peticin en realidad sera algo parecido a lo siguiente: GET / HTTP/1.1 Host: xkcd.com Accept: text/html User-Agent: Mozilla/5.0 (Macintosh) Este sencillo mensaje comunica todo lo necesario sobre qu recursos exactamente solicita el cliente. La primera lnea de una peticin HTTP es la ms importante y contiene dos cosas: la URI y el mtodo HTTP. La URI (por ejemplo, /, /contacto, etc.) es la direccin o ubicacin que identifica unvocamente al recurso que solicita el cliente. El mtodo HTTP (por ejemplo, GET) define lo que quieres hacer con el recurso. Los mtodos HTTP son los verbos de la peticin y definen las pocas formas en que puedes actuar sobre el recurso: Mtodo Accin GET Recupera el recurso desde el servidor POST Crea un recurso en el servidor Mtodo Accin PUT Actualiza el recurso en el servidor DELETE Elimina el recurso del servidor Teniendo esto en cuenta, puedes imaginar cmo sera por ejemplo la peticin HTTP necesaria para borar un artculo especfico de un blog: DELETE /blog/15 HTTP/1.1 NOTA En realidad, hay nueve mtodos HTTP definidos por la especificacin HTTP, pero muchos de ellos no se utilizan o no estn soportados. De hecho, muchos navegadores modernos no soportan los mtodos PUT y DELETE. Adems de la primera lnea, una peticin HTTP contiene tambin otras lneas de informacin conocidas como cabeceras de peticin. Las cabeceras proporcionan mucha informacin, como el servidor (o host) solicitado, los formatos de respuesta que acepta el cliente (Accept) y la aplicacin que utiliza el cliente para realizar la peticin (User-Agent). Existen muchas otras cabeceras y se pueden encontrar en el artculo Lista de campos de las cabeceras HTTP en la Wikipedia. 1.1.2. Paso 2: El servidor devuelve una respuesta Una vez que un servidor ha recibido la peticin, sabe exactamente qu recursos necesita el cliente (a travs de la URI) y lo que el cliente quiere hacer con ese recurso (a travs del mtodo). Por ejemplo, en el caso de una peticin GET, el servidor prepara el recurso y lo devuelve en una respuesta HTTP. Considera la respuesta del servidor web del sitio xkcd:
Figura 1.3 Respuesta HTTP para obtener la tira cmica ms reciente de Xkcd Traducida a HTTP, la respuesta enviada de vuelta al navegador es similar a lo siguiente: HTTP/1.1 200 OK Date: Sat, 02 Apr 2011 21:05:05 GMT Server: lighttpd/1.4.19 Content-Type: text/html
<html> <!-- HTML de la tira cmica de Xkcd --> </html> La respuesta HTTP contiene el recurso solicitado (en este caso, el contenido HTML de una pgina web), as como otra informacin acerca de la respuesta. La primera lnea es especialmente importante y contiene el cdigo de estado HTTP (200 en este caso) de la respuesta. El cdigo de estado indica el resultado global de la peticin devuelta al cliente. Tuvo xito la peticin? Hubo algn error? Existen diferentes cdigos de estado que indican xito, error o qu ms se necesita hacer con el cliente (por ejemplo, redirigirlo a otra pgina). La lista completa se puede encontrar en el artculo Lista de cdigos de estado HTTP en la Wikipedia. Al igual que la peticin, una respuesta HTTP contiene datos adicionales conocidos como cabeceras HTTP. Por ejemplo, una cabecera importante de la respuesta HTTP es Content-Type. Un mismo recurso se puede devolver en varios formatos diferentes (HTML, XML, JSON, etc.) y la cabeceraContent-Type dice al cliente qu formato se ha utilizado (para ello utiliza valores estndar comotext/html que se conocen como Internet Media Types). Puedes encontrar la lista completa de tipos de contenido en el artculo Lista de tipos de contenido de Internet en la Wikipedia. Existen muchas otras cabeceras, algunas de las cuales son muy importantes. Por ejemplo, ciertas cabeceras se pueden usar para crear un sistema de memoria cach bastante interesante. 1.1.3. Peticiones, respuestas y desarrollo web Esta conversacin peticin-respuesta es el proceso fundamental en el que se basa toda la comunicacin en la web. Y a pesar de ser tan importante y poderoso, al mismo tiempo es muy sencillo. El concepto ms importante es el siguiente: independientemente del lenguaje que utilices, el tipo de aplicacin que construyas (web, mvil, API), o la filosofa de desarrollo que sigas, el objetivo final de una aplicacin siempre es entender cada peticin y crear y devolver la respuesta adecuada. Symfony est diseado para adaptarse a esta realidad. TRUCO Puedes obtener ms informacin acerca de la especificacin HTTP, en la referencia originalHTTP 1.1 RFC. Tambin puedes leer la referencia HTTP Bis, que es una versin actualizada y ms detallada de la referencia anterior. Una gran herramienta para comprobar tanto la peticin como las cabeceras de la respuesta mientras navegas es la extensin Live HTTP Headers de Firefox o elInspector Web de los navegadores Chrome y Safari. 1.2. Peticiones y respuestas en PHP Cmo interactas con la "peticin" y creas una "respuesta" utilizando PHP? En realidad, PHP te abstrae un poco de todo el proceso: <?php $uri = $_SERVER['REQUEST_URI']; $foo = $_GET['foo'];
header('Content-type: text/html'); echo 'La URI solicitada es: '.$uri; echo 'El valor del parmetro "foo" es: '.$foo; Por extrao que parezca, esta pequea aplicacin est obteniendo informacin de la peticin HTTP y la utiliza para crear una respuesta HTTP. En lugar de analizar el mensaje HTTP de la peticin, PHP crea variables superglobales como $_SERVER y $_GET que contienen toda la informacin de la peticin. Del mismo modo, en lugar de devolver la respuesta HTTP con formato de texto, puedes usar la funcin header() para crear las cabeceras de la respuesta y simplemente imprimir el contenido que se enviar en el mensaje de la respuesta. Despus PHP crea la verdadera respuesta HTTP que se devuelve al cliente: HTTP/1.1 200 OK Date: Sat, 03 Apr 2011 02:14:33 GMT Server: Apache/2.2.17 (Unix) Content-Type: text/html
La URI solicitada es: /testing?foo=symfony El valor del parmetro "foo" es: symfony 1.3. Peticiones y respuestas en Symfony Symfony ofrece una alternativa al enfoque de PHP a travs de dos clases que te permiten interactuar con la peticin HTTP y la respuesta de una manera ms fcil. La clase Request representa la peticin HTTP siguiendo la filosofa de orientacin a objetos. Con ella, tienes toda la informacin a tu alcance: use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
// la URI solicitada (p.e. /contacto) menos algunos parmetros de la consult a $request->getPathInfo();
// recupera las variables GET y POST respectivamente $request->query->get('foo'); $request->request->get('bar', 'valor utilizado si "bar" no existe');
// recupera las variables de SERVER $request->server->get('HTTP_HOST');
// recupera una instancia del archivo subido identificado por 'foo' $request->files->get('foo');
// recupera un valor de una COOKIE $request->cookies->get('PHPSESSID');
// recupera una cabecera HTTP de la peticin, normalizada, con ndices en mi nscula $request->headers->get('host'); $request->headers->get('content_type');
$request->getMethod(); // GET, POST, PUT, DELETE, HEAD $request->getLanguages(); // un array de idiomas aceptados por el cliente Una ventaja aadida es que la clase Request hace un montn de trabajo adicional del que no tienes que preocuparte. Por ejemplo, el mtodo isSecure() internamente comprueba tres valores PHP diferentes que pueden indicar si el usuario est conectado a travs de una conexin segura (es decir,https). ParameterBags y atributos de la peticinComo vimos anteriormente, las variables $_GET y $_POST son accesibles a travs de las propiedadesquery y request de la clase Request, respectivamente. Cada uno de estos objetos es un objeto de la clase ParameterBag, la cual cuenta con mtodos cmo get(), has() y all() entre otros. De hecho, todas las propiedades pblicas utilizadas en el cdigo del ejemplo anterior son un ejemplo del ParameterBag. La clase Request tambin tiene una propiedad pblica attributes, que almacena informacin especial relacionada sobre el funcionamiento interno de la aplicacin. En Symfony2, la propiedadattibutes guarda por ejemplo los valores relacionados con el sistema de enrutamiento que se explicar ms adelante (como por ejemplo, _controller y _route). El propsito de la propiedadattributes es el de preparar y almacenar informacin del contexto especfico de la peticin. Symfony tambin proporciona una clase Response, que simplifica la representacin de la respuesta HTTP en PHP. Esto permite que tu aplicacin utilice una interfaz orientada a objetos para construir la respuesta que ser devuelta al cliente: use Symfony\Component\HttpFoundation\Response;
// imprime las cabeceras HTTP seguidas por el contenido $response->send(); NOTA Las constantes que definen los cdigos de estado de HTTP (como por ejemploResponse::HTTP_OK) fueron aadidas en la versin 2.4 de Symfony. Si Symfony no ofreciera nada ms, ya tendras un conjunto de herramientas para acceder fcilmente a la informacin de la peticin y una interfaz orientada a objetos para crear la respuesta. De hecho, a medida que aprendas muchas de las caractersticas avanzadas de Symfony, nunca debes olvidar que el objetivo de tu aplicacin es interpretar una peticin y crear la respuesta adecuada basada en la lgica de tu aplicacin. TRUCO Las clases Request y Response forman parte de un componente independiente incluido en Symfony llamado HttpFoundation. Este componente se puede utilizar en aplicaciones independientes de Symfony y tambin proporciona clases para manejar sesiones y subir archivos. 1.4. El viaje desde la peticin hasta la respuesta Al igual que el mismo HTTP, los objetos Peticin y Response son bastante simples. La parte difcil de la construccin de una aplicacin es escribir lo que viene en el medio. En otras palabras, el verdadero trabajo viene al escribir el cdigo que interpreta la informacin de la peticin y crea la respuesta. Tu aplicacin probablemente hace muchas cosas, como enviar correo electrnico, manejar los formularios presentados, guardar cosas en una base de datos, reproducir las pginas HTML y proteger el contenido con seguridad. Cmo puedes manejar todo esto y al mismo tiempo conseguir que tu cdigo est organizado y sea fcil de mantener? Symfony fue creado precisamente para resolver estos problemas y para que no tengas que hacerlo a mano. 1.4.1. El controlador frontal Antiguamente, las aplicaciones web se construan de modo que cada pgina web del sitio tena su propio archivo fsico: index.php contacto.php blog.php Esta filosofa de trabajo tiene varios problemas, como la falta de flexibilidad de las URL (qu pasa si quieres cambiar blog.php a noticias.php sin romper todos tus enlaces?) y el hecho de que cada archivo debe incluir a mano todos los archivos necesarios para la seguridad, conexiones a base de datos y para aplicar los estilos grficos del sitio. Una solucin mucho mejor es usar un controlador frontal, que es un solo archivo PHP que se encarga de servir todas las peticiones que llegan a tu aplicacin. Por ejemplo: Archivo solicitado Archivo realmente ejecutado /index.php index.php /index.php/contacto index.php /index.php/blog index.php TRUCO Usando el mdulo mod_rewrite de Apache (o el equivalente en otros servidores web), lasURL se pueden limpiar fcilmente para que quiten la parte del index.php y se queden simplemente en /, /contacto y /blog. Ahora, todas las peticiones se manejan exactamente igual. En lugar de URL individuales ejecutando diferentes archivos PHP, el controlador frontal siempre se ejecuta, y la redireccin de cada URL a una parte diferente de la aplicacin se realiza internamente. Esto resuelve los problemas comentados anteriormente. Casi todas las aplicaciones web modernas siguen la filosofa del controlador frontal, incluyendo aplicaciones como WordPress. 1.4.2. Mantente organizado Una vez dentro del controlador frontal, cmo sabes qu pgina debes generar y cmo puedes generar todas las pginas sin que la aplicacin se vuelva catica? El truco consiste en comprobar la URI entrante y ejecutar diferentes partes de tu cdigo en funcin de ese valor. Aunque es bastantechapucero, el siguiente cdigo te podra servir para ello: // Contenido del archivo index.php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals(); $path = $request->getPathInfo(); // La ruta URI solicitada
if (in_array($path, array('', '/')) { $response = new Response('Bienvenido a nuestra portada.'); } elseif ($path == '/contacto') { $response = new Response('Contctanos'); } else { $response = new Response('Pgina no encontrada.', Response::HTTP_NOT_FOU ND); } $response->send(); Escalar el cdigo anterior para una aplicacin real es un problema difcil de resolver. Afortunadamente esto es exactamente para lo que Symfony est diseado. 1.4.3. El flujo de las aplicaciones Symfony Cuando dejas que Symfony controle cada peticin, tu vida como programador es mucho ms fcil. Symfony sigue el mismo patrn simple en cada peticin. Las peticiones entrantes son interpretadas por el enrutador y pasadas a las funciones controladoras, que devuelven objetos Response.
Figura 1.4 Flujo de la peticin en Symfony2 Cada pgina de tu sitio est definida en un archivo de configuracin de rutas que asigna a cada URL una funcin PHP diferente. El trabajo de cada funcin PHP (conocida como controlador), es utilizar la informacin de la peticin junto con muchas otras herramientas que Symfony pone a tu disposicin para crear y devolver un objeto Response. En otras palabras, el controlador es donde est tu cdigo: ah es dnde se interpreta la peticin y se crea una respuesta. As de fcil! Repasemos: Cada peticin ejecuta un archivo controlador frontal; El sistema de enrutado determina qu funcin PHP se ejecuta en base a la informacin de la peticin y la configuracin de enrutado que hemos creado; Se ejecuta la funcin PHP adecuada, donde tu cdigo crea y devuelve el objeto Response. 1.4.4. Una peticin Symfony en la prctica Sin entrar demasiado en los detalles, veamos este proceso en la prctica. Supongamos que deseas agregar una pgina /contacto a tu aplicacin Symfony. En primer lugar, empezamos agregando una entrada /contacto a tu archivo de configuracin de rutas: YAML XML PHP # app/config/routing.yml contacto: path: /contacto defaults: { _controller: AcmeDemoBundle:Main:contacto } NOTA En este ejemplo utilizamos YAML para definir la configuracin de enrutado, pero tambin se pueden utilizar los formatos XML y PHP. Cuando alguien visita la pgina /contacto, Symfony2 detecta que se trata de esta ruta y se ejecuta el controlador especificado. Como veremos en el captulo de enrutamiento, la cadenaAcmeDemoBundle:Main:contacto es un atajo que apunta al mtodo contactoAction() de PHP dentro de una clase llamada MainController: // src/Acme/DemoBundle/Controller/MainController.php namespace Acme\DemoBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class MainController { public function contactoAction() { return new Response('<h1>Contctanos</h1>'); } } En este ejemplo sencillo, el controlador simplemente crea un objeto Response con el cdigo HTML "<h1>Contctanos</h1>". En el captulo dedicado a los controladores aprenders cmo hacer que los controladores utilicen plantillas para generar el contenido HTML en vez de tener que escribirlo directamente en el objeto Response. Esto hace que el controlador deba preocuparse slo de las cosas difciles: la interaccin con la base de datos, la manipulacin de la informacin o el envo de mensajes de correo electrnico. 1.5. Symfony2: construye tu aplicacin, no tus herramientas Ahora sabemos que el objetivo de cualquier aplicacin es interpretar cada peticin entrante y crear una respuesta adecuada. Cuando una aplicacin crece, es ms difcil organizar tu cdigo y que a la vez sea fcil de mantener. Adems, siempre te vas a encontrar con las mismas tareas complejas: la persistencia de informacin a la base de datos, procesar y reutilizar plantillas, manejar los formularios, enviar mensajes de correo electrnico, validar los datos del usuario, administrar la seguridad del sitio web, etc. La buena noticia es que todos los programadores web se encuentran con esos mismos problemas. As que Symfony proporciona una plataforma completa, con herramientas que te permiten construir tu aplicacin, no tus herramientas. Con Symfony2, nada se te impone: eres libre de usar la plataforma Symfony completa, o simplemente aquellas partes de Symfony que quieras. 1.5.1. Herramientas independientes: los componentes de Symfony2 Con todo lo anterior, qu es exactamente Symfony2?. En primer lugar, Symfony2 es una coleccin de ms de veinte libreras independientes que se pueden utilizar dentro de cualquier proyecto PHP. Estas libreras, llamadas componentes de Symfony2, son bastante tiles prticamente para cualquier aplicacin, independientemente de cmo desarrolles tu proyecto. Las siguientes son algunas de las ms destacadas: HttpFoundation: contiene las clases Request y Response, as como otras clases para manejar sesiones y cargar archivos. Routing: potente y rpido sistema de enrutado que te permite asignar una URI especfica (por ejemplo /contacto) a cierta informacin acerca de cmo se debe manejar dicha peticin (por ejemplo, ejecutar el mtodo contactoAction()). Form: una completa y flexible plataforma para crear formularios y procesar los datos presentados en ellos. Validator: un sistema para crear reglas sobre datos y as comprobar si los datos que presenta el usuario son vlidos o no siguiendo esas reglas. ClassLoader: una librera para cargar automticamente clases PHP sin necesidad de aadir instrucciones require a mano en los archivos que contienen esas clases. Templating: juego de herramientas para utilizar plantillas, que soporta desde la herencia de plantillas (es decir, una plantilla est decorada con un diseo) y hasta otras tareas comunes de las plantillas. Security: una poderosa librera para manejar todo tipo de seguridad dentro de una aplicacin. Translation: plataforma para traducir cadenas de texto en tu aplicacin. Todos y cada uno de estos componentes estn desacoplados, lo que significa que puedes utilizarlos en cualquier proyecto PHP, independientemente de si utilizas la plataforma Symfony2. Cada componente est diseado para utilizarlo si es conveniente o para sustituirlo cuando sea necesario. 1.5.2. La solucin completa: la plataforma Symfony2 Qu es la plataforma Symfony2? La plataforma Symfony2 es una librera PHP que realiza dos tareas diferentes: Proporciona una seleccin de componentes Symfony2 y algunas libreras de terceros (por ejemplo, SwiftMailer para enviar mensajes de correo electrnico). Define una configuracin adecuada e incluye una capa que integra todas las diferentes partes (componentes, libreras, etc.) El objetivo de la plataforma es integrar muchas herramientas independientes con el fin de proporcionar una experiencia coherente al desarrollador. Symfony2 proporciona un potente conjunto de herramientas para desarrollar aplicaciones web rpidamente sin afectar excesivamente a tu forma de trabajar. Si ests comenzando con Symfony2, lo mejor es que utilices una distribucin de Symfony2, que proporciona un esqueleto de un proyecto Symfony2 de prueba con varios parmetros ya preconfigurados. Si eres un usuario avanzado, puedes crearte tu propia distribucin o incluso utilizar componentes individuales de Symfony2 Captulo 2. De PHP a Symfony2 Por qu Symfony2 es mejor que escribir cdigo PHP a pelo? Si nunca has usado una plataforma PHP, o no ests familiarizado con la filosofa MVC, o simplemente te preguntas qu es todo ese ruidogenerado en torno a Symfony2, este captulo es para ti. En vez de contarte que Symfony2 te permite desarrollar software ms rpido y mejor que con PHP simple, vas a poder comprobarlo tu mismo. En este captulo, vamos a escribir una aplicacin sencilla en PHP simple, y luego la reconstruiremos para que est mejor organizada. Podrs viajar a travs del tiempo, viendo las decisiones de por qu el desarrollo web ha evolucionado en los ltimos aos hasta donde est ahora. Al final del captulo, vers cmo Symfony2 se encarga de todas las tareas comunes (y aburridas) mientras que te permite recuperar el control de tu cdigo. 2.1. Un blog sencillo creado con PHP simple En este captulo, crearemos la tpica aplicacin de blog utilizando slo PHP simple. Para empezar, crea una pgina que muestre las entradas del blog que se han persistido en la base de datos. Escribirla en PHP simple es rpido, pero un poco sucio: <?php // index.php
<?php mysql_close($link); El cdigo anterior es fcil de escribir y se ejecuta muy rpido, pero en cuanto la aplicacin crece, es muy difcil de mantener. Sus principales problemas son los siguientes: No hay comprobacin de errores: qu sucede si falla la conexin a la base de datos? Organizacin deficiente: si la aplicacin crece, este nico archivo cada vez ser ms difcil de mantener, hasta que finalmente sea imposible. Dnde se debe colocar el cdigo para manejar los formularios? Cmo se pueden validar los datos? Dnde debe ir el cdigo para enviar mensajes de correo electrnico? Es difcil reutilizar el cdigo: ya que todo est en un archivo, no hay manera de volver a utilizar alguna parte de la aplicacin en otras pginas del blog. NOTA Otro problema no mencionado aqu es el hecho de que la base de datos est vinculada a MySQL. Aunque no se ha tratado aqu, Symfony2 integra Doctrine, una librera que permite abstraer el acceso a la base de datos y el manejo de la informacin. Vamos a trabajar a continuacin en la solucin de estos y muchos otros problemas ms. 2.1.1. Aislando la parte de la vista El cdigo se puede mejorar fcilmente separando la lgica de la aplicacin (cdigo PHP puro) y "la parte de la vista" o "presentacin", que est formada por todo lo relacionado con el cdigo HTML: <?php // index.php
// incluye el cdigo HTML de la vista require 'templates/list.php'; Ahora el cdigo HTML est guardado en un archivo separado (templates/list.php). Este archivo contiene todo el cdigo HTML junto con algunas pocas instrucciones PHP que utilizan la sintaxis alternativa recomendada para las plantillas PHP: <html> <head> <title>List of Posts</title> </head> <body> <h1>List of Posts</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="/read?id=<?php echo $post['id'] ?>"> <?php echo $post['title'] ?> </a> </li> <?php endforeach; ?> </ul> </body> </html> Por convencin, el archivo que contiene toda la lgica de la aplicacin (index.php) se conoce como"controlador". El trmino controlador es una palabra que se utiliza mucho, independientemente del lenguaje o plataforma que utilices. Simplemente se refiere a la zona de tu cdigo que procesa la peticin del usuario y prepara la respuesta. En este caso, nuestro controlador obtiene los datos de la base de datos y, luego los incluye en una plantilla para presentarlos al usuario. Con el controlador aislado, podramos cambiar fcilmente slo el archivo de la plantilla si queremos servir los contenidos del blog en otro formato (por ejemplo,list.json.php para el formato JSON). 2.1.2. Aislando la lgica de la aplicacin (el dominio) Por ahora la aplicacin slo contiene una pgina. Pero qu pasa si una segunda pgina necesita utilizar la misma conexin a la base de datos, e incluso el mismo resultado de la bsqueda de las entradas del blog? La solucin consiste en refactorizar el cdigo para que todas estas funciones bsicas de acceso a datos de la aplicacin estn aisladas en un nuevo archivo llamado model.php: <?php // model.php
function open_database_connection() { $link = mysql_connect('localhost', 'myuser', 'mypassword'); mysql_select_db('blog_db', $link);
return $link; }
function close_database_connection($link) { mysql_close($link); }
function get_all_posts() { $link = open_database_connection();
$result = mysql_query('SELECT id, title FROM post', $link); $posts = array(); while ($row = mysql_fetch_assoc($result)) { $posts[] = $row; } close_database_connection($link);
return $posts; } TRUCO Se utiliza el nombre model.php para el archivo porque el acceso a la lgica y los datos de una aplicacin se conoce tradicionalmente como la capa del "modelo". En una aplicacin bien organizada, la mayora del cdigo que representa tu "lgica de negocio" debe estar en el modelo (en lugar del controlador). Y, a diferencia de este ejemplo, slo una parte (o ninguna) del modelo realmente est interesada en acceder a la base de datos. El controlador (index.php) ahora es muy sencillo: <?php require_once 'model.php';
$posts = get_all_posts();
require 'templates/list.php'; Ahora, la nica tarea del controlador es conseguir los datos de la capa del modelo de la aplicacin (el modelo) y utilizar una plantilla para mostrar los datos. Este es un ejemplo muy simple del patrn modelo - vista - controlador. 2.1.3. Aislando el diseo La aplicacin ahora est dividida en tres partes distintas, lo que nos ofrece varias ventajas y la oportunidad de volver a utilizar casi todo en diferentes pginas. La nica parte del cdigo que no se puede reutilizar es el diseo HTML + CSS de la pgina. Vamos a solucionar este problema creando un nuevo archivo llamado base.php: <!-- templates/base.php --> <html> <head> <title><?php echo $title ?></title> </head> <body> <?php echo $content ?> </body> </html> La plantilla original (templates/list.php) ahora se puede simplificar para que utilice el archivobase.php anterior como base: <?php $title = 'List of Posts' ?>
<?php include 'base.php' ?> El cdigo anterior introducido una metodologa que nos permite reutilizar el diseo. Desafortunadamente, para conseguirlo estamos obligados a utilizar en la plantilla algunas funciones de PHP poco recomendables (ob_start(), ob_get_clean()). Symfony2 utiliza un componente de plantillas (Templating) que nos permite realizar esto de una manera ms limpia y sencilla. En breve lo 2.2. Agregando una pgina show al blog La pgina list del blog se ha rediseado para que el cdigo est mejor organizado y sea reutilizable. Para probar que esto es as, aade una pgina show al blog, que muestre una entrada individual del blog identificada por un parmetro de consulta id. Para empezar, crea una nueva funcin en el archivo model.php que recupere un resultado individual del blog basndose en un identificador dado: // model.php function get_post_by_id($id) { $link = open_database_connection();
$id = mysql_real_escape_string($id); $query = 'SELECT date, title, body FROM post WHERE id = '.$id; $result = mysql_query($query); $row = mysql_fetch_assoc($result);
close_database_connection($link);
return $row; } A continuacin, crea un nuevo archivo llamado show.php, que ser el controlador para esta nueva pgina: <?php require_once 'model.php';
$post = get_post_by_id($_GET['id']);
require 'templates/show.php'; Por ltimo, crea el nuevo archivo de plantilla (templates/show.php) para mostrar una entrada individual del blog: <?php $title = $post['title'] ?>
<?php include 'base.php' ?> Como has visto, es muy fcil crear la segunda pgina sin duplicar cdigo. Sin embargo, esta nueva pgina introduce algunos problemas adicionales que una plataforma web puede resolver por ti. Por ejemplo, si el usuario proporciona un valor ilegal para el parmetro id o un valor vaco, la consulta har que la pgina se bloquee. En este caso, sera mejor que la aplicacin generara una pgina de error de tipo 404 (algo que no es fcil con el cdigo actual de la aplicacin). Peor an, si no te acuerdas de limpiar con la funcin mysql_real_escape_string() el parmetro id que te pasa el usuario, tu base de datos sera vulnerable a los ataques de tipo inyeccin SQL. Otro problema importante es que cada archivo de tipo controlador debe incluir el archivo model.php. Qu pasara si cada archivo de controlador de repente tuviera que incluir un archivo adicional o realizar alguna tarea global (por ejemplo, reforzar la seguridad)? Tal como est ahora, el cdigo tendra que incluir a mano todos los archivos de los controladores. Si olvidas incluir algo en un solo archivo, esperamos que no sea alguno relacionado con la seguridad. 2.3. El controlador frontal al rescate Una solucin mucho mejor es usar un controlador frontal: un nico archivo PHP a travs del cual se procesen todas las peticiones del usuario. Con un controlador frontal, la URI de la aplicacin cambia un poco, pero se vuelve mucho ms flexible: Sin controlador frontal: /index.php => (ejecuta index.php) la pgina que lista los artculos. /show.php => (ejecuta show.php) la pgina que muestra un artculo es pecfico.
Con index.php como controlador frontal /index.php => (ejecuta index.php) la pgina que lista los artculos. /index.php/show => (ejecuta index.php) la pgina que muestra un artculo es pecfico. 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 dems servidores web). En ese caso, la URI resultante de la pgina show del blog sera simplemente /show. Cuando se usa un controlador frontal, un solo archivo PHP (index.php en este caso) procesa todas las peticiones. Para la pgina show del blog, /index.php/show realmente ejecuta el archivo index.php, que ahora es el responsable de dirigir internamente las peticiones basndose en la URI completa. Como puedes ver, un controlador frontal es una herramienta muy poderosa. 2.3.1. Creando el controlador frontal Ests a punto de dar un gran paso en la aplicacin. Con un archivo manejando todas las peticiones, puedes centralizar cosas como el manejo de la seguridad, la carga de la configuracin y el enrutamiento. En nuestra aplicacin, index.php ahora debe ser lo suficientemente inteligente como para mostrar la lista de entradas del blog o mostrar la pgina de una entrada particular basndose en la URI solicitada: <?php // index.php
// carga e inicia algunas libreras globales require_once 'model.php'; require_once 'controllers.php';
// encamina la peticin 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 cdigo, 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 aplicacin (model.php y controllers.php) y la decisin 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 manipulacin y enrutado de peticiones. TRUCO Otra ventaja del controlador frontal es la flexibilidad de las URL. Ten en cuenta que la URL de la pgina show del blog se puede cambiar de /show a /read cambiando el cdigo en un nico lugar. Antes, era necesario cambiar todo un archivo para cambiar el nombre. En Symfony2, las URL son incluso ms flexibles. Por ahora, la aplicacin ha evolucionado de un nico archivo PHP, a una estructura organizada que permite la reutilizacin de cdigo. Debes estar contento, pero an lejos de estar satisfecho. Por ejemplo, el sistema de enrutamiento es muy mejorable, ya que no reconoce por ejemplo que la pginalist (/index.php) tambin debe ser accesible a travs de / (si has agregado las reglas de reescritura de Apache). Adems, en lugar de programar el blog, has perdido tu tiempo en preparar la "arquitectura" del cdigo (por ejemplo, el enrutamiento, los controladores, las plantillas, etc.) Y pronto tendrs que perder ms tiempo en la gestin de los formularios, la validacin de la informacin, crear un archivo de log, la gestin 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. Aadiendo 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 versin correcta de los archivos de Symfony, todas sus dependencias y tambin proporciona un cargador automtico de clases (llamadoclass loader en ingls). Un cargador automtico es una herramienta que permite empezar a utilizar clases PHP sin incluir explcitamente el archivo que contiene la clase. Dentro del directorio raz del proyecto crea un archivo llamado composer.json con el siguiente contenido: { "require": { "symfony/symfony": "2.4.*" }, "autoload": { "files": ["model.php","controllers.php"] } } A continuacin, descarga Composer y ejecuta despus el siguiente cdigo para descargar Symfony dentro del directorio vendor/ del proyecto: $ php composer.phar install Adems de descargar todas las dependencias, Composer genera un archivo llamadovendor/autoload.php, que se encarga de cargar automticamente todas las clases del frameworkSymfony y de todas las libreras que aadas en la seccin autoload del archivo composer.json. La esencia de la filosofa Symfony es que el trabajo principal de una aplicacin es interpretar cada peticin y devolver una respuesta. Con este fin, Symfony2 proporciona las clases Request y Response. Estas clases son representaciones orientadas a objetos de la peticin 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;
// enva las cabeceras y la respuesta $response->send(); Los controladores se encargan de devolver un objeto Response. Para simplificar la generacin del cdigo HTML de la respuesta, crea una nueva funcin llamada render_template(), la cual, por cierto, acta un poco como el motor de plantillas de Symfony2: // controllers.php use Symfony\Component\HttpFoundation\Response;
// funcin 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 aadido una pequesima parte de las ideas de Symfony2, la aplicacin ahora es ms flexible y fiable. El objeto Request proporciona una manera segura para acceder a la informacin de la peticin HTTP. El mtodo 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 aplicacin es lo suficientemente inteligente para encaminar la peticin hacia show_action(). El objeto Response proporciona flexibilidad al construir la respuesta HTTP, permitiendo que las cabeceras HTTP y el contenido se definan a travs de una interfaz orientada a objetos. Y aunque las respuestas en esta aplicacin son simples, esta flexibilidad ser muy til en cuanto tu aplicacin crezca. 2.3.3. Aplicacin de ejemplo en Symfony2 El blog ha mejorado bastante, pero todava contiene una gran cantidad de cdigo para ser una aplicacin tan simple. Durante las mejoras de la aplicacin, se ha creado un sistema de enrutamiento muy sencillo y otro mtodo 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 aplicacin 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();
public function showAction($id) { $post = $this->get('doctrine') ->getManager() ->getRepository('AcmeBlogBundle:Post') ->find($id);
if (!$post) { // hace que se muestre la pgina de error 404 throw $this->createNotFoundException(); }
return $this->render('AcmeBlogBundle:Blog:show.html.php', array('pos t' => $post)); } } Los dos controladores siguen siendo ms o menos pequeos. Cada uno utiliza la librera ORM de Doctrine para recuperar objetos de la base de datos y el componente Templating para generar el cdigo HTML con una plantilla y devolver un objeto Response. La plantilla list ahora es un poco ms 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 diseo de la plantilla base tambin es muy parecido al de antes: <!-- app/Resources/views/base.html.php --> <html> <head> <title><?php echo $view['slots']->output('title', 'Default title') ?></t itle> </head> <body> <?php echo $view['slots']->output('_content') ?> </body> </html> NOTA Te vamos a dejar como ejercicio la plantilla show, porque debera ser trivial crearla basndote en la plantilla list. Cuando arranca el motor de Symfony2 (llamado kernel), necesita un mapa para saber qu controladores ejecutar en base a la informacin solicitada por los usuarios. Este mapa se crea con la configuracin de enrutamiento, que proporciona esta informacin 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 distribucin Symfony2, ni siquiera tendrs 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 objetoRequest para que lo manipule. Despus, el ncleo de Symfony2 utiliza la informacin de enrutamiento para determinar qu controlador se ejecuta. Al igual que antes, el mtodo controlador es el responsable de devolver el objeto Response final. Y eso es todo lo que hace el controlador frontal. 2.3.4. Qu ms ofrece Symfony2 En los siguientes captulos, aprenders ms acerca de cmo funciona cada parte de Symfony y la organizacin recomendada de un proyecto. Ahora vamos a ver cmo nos ha facilitado nuestro trabajo de programadores el migrar el blog de PHP simple a Symfony2: Tu aplicacin cuenta con cdigo claro y bien organizado (aunque Symfony no te obliga a ello). Esto facilita la reutilizacin de cdigo y permite a los nuevos desarrolladores ser productivos en el proyecto con mayor rapidez. El 100% del cdigo que escribes es para tu aplicacin. No necesitas desarrollar o mantener herramientas de bajo nivel como la carga automtica de clases, el enrutamiento o los controladores. Symfony2 te proporciona acceso a herramientas de software libre tales como Doctrine, plantillas, seguridad, formularios, validacin y traduccin (por nombrar algunas). La aplicacin 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 ms avanzadas como Varnish). Esto se explica ms adelante en el captulo sobre la cach. Y lo mejor de todo, utilizando Symfony2, ahora tienes acceso a un conjunto de herramientas desoftware libre de alta calidad desarrolladas por la comunidad Symfony2! Puedes encontrar una buena coleccin de herramientas comunitarias de Symfony2 enKnpBundles.com. 2.4. Mejorando las plantillas Si quieres utilizarlo, Symfony2 incluye de serie un motor de plantillas llamado Twig que hace que las plantillas se escriban ms rpido y sean ms fciles de leer. En otras palabras, la aplicacin de ejemplo desarrollada anteriormente podra incluso tener menos cdigo. Considera por ejemplo la siguiente plantilla list reescrita con Twig: {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
{% extends "::base.html.twig" %} {% block title %}List of Posts{% endblock %}
{% block body %} <h1>List of Posts</h1> <ul> {% for post in posts %} <li> <a href="{{ path('blog_show', { 'id': post.id }) }}"> {{ post.title }} </a> </li> {% endfor %} </ul> {% endblock %} Tambin es muy fcil escribir la plantilla base.html.twig correspondiente: {# app/Resources/views/base.html.twig #}
<html> <head> <title>{% block title %}Default title{% endblock %}</title> </head> <body> {% block body %}{% endblock %} </body> </html> Twig est completamente integrado en Symfony2. Aunque Symfony2 siempre soportar las plantillas PHP, en los siguientes captulos se van a seguir presentando algunas de las muchas ventajas de Twig respecto a las plantillas PHP. Para ms informacin, consulta el captulo sobre las plantillas. Captulo 4. Creando pginas en Symfony2 Crear una nueva pgina en Symfony2 es un proceso muy sencillo de dos pasos: Crear una ruta: una ruta define la URL de tu pgina (por ejemplo /sobre) y especifica el controlador (que en realidad es una funcin PHP) que Symfony2 ejecuta cuando la URL de la peticin del usuario coincida con el patrn de la ruta. Crear un controlador: un controlador es una funcin PHP que obtiene la peticin del usuario y la transforma en el objeto Response con el que Symfony2 crea la respuesta que se enva al usuario . Nos encanta este enfoque simple porque coincide con la forma en que funciona la Web. Cada interaccin en la Web se inicia con una peticin HTTP. El trabajo de la aplicacin simplemente es interpretar la peticin y devolver la respuesta HTTP adecuada. Symfony2 sigue esta filosofa y te proporciona las herramientas y convenciones para mantener organizada tu aplicacin a medida que crece en usuarios y complejidad. 4.1. La pgina Hola Symfony! Vamos a empezar con una aplicacin derivada del clsico "Hola Mundo!". Cuando hayamos terminado, el usuario podr recibir un saludo personal (por ejemplo, "Hola Symfony") al ir a la siguiente URL: http://localhost/app_dev.php/hello/Symfony Ms adelante podrs sustituir la palabra Symfony con cualquier otro nombre al que darle la bienvenida. Para crear la pgina, sigue el proceso simple de dos pasos. NOTA Este captulo supone que ya has descargado Symfony2 y configurado tu servidor web. En la URL del ejemplo anterior se supone que localhost apunta al directorio web de tu nuevo proyecto Symfony2. Para obtener una informacin ms detallada sobre cmo hacerlo, consulta la documentacin del servidor Web que ests usando. Estos son los enlaces a las pginas de documentacin adecuadas para los dos servidores web ms populares: Para el servidor Apache, consulta la documentacin de Apache sobre DirectoryIndex. Para Nginx, consulta la documentacin de ubicacin HttpCoreModule de Nginx. 4.2. Antes de empezar, crea el bundle Antes de empezar, tendrs que crear un bundle (que se podra traducir como "paquete"). En Symfony2, un bundle es como un plugin, con la salvedad de que todo el cdigo de tu aplicacin se almacena dentro de bundles. Un bundle no es ms que un directorio que almacena todo lo relacionado con una funcin especfica, incluyendo clases PHP, configuracin, 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 captulo), 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. Adems se ha aadido automticamente una lnea en el archivo app/AppKernel.php para registrar el bundle en el ncleo 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 aplicacin dentro del bundle. 4.2.1. Paso 1: Creando la ruta Por defecto, el archivo de configuracin de enrutamiento en una aplicacin Symfony2 se encuentra enapp/config/routing.yml. Si lo prefieres, y al igual que en el resto de la configuracin en Symfony2, puedes utilizar el formato XML o PHP para configurar tus rutas. Si te fijas en el archivo de enrutamiento principal, vers que Symfony ya ha agregado una entrada al generar el bundle AcmeHelloBundle: YAML XML PHP # app/config/routing.yml acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: / Esta directiva de configuracin le dice a Symfony que cargue la configuracin 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 aplicacin y despus las importas desde ese archivo. Ahora que el archivo routing.yml del bundle se importa desde el archivo de enrutamiento principal de la aplicacin, aade la nueva ruta que define la URL de la pgina que ests a punto de crear: YAML XML PHP # src/Acme/HelloBundle/Resources/config/routing.yml hello: path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } La ruta se compone bsicamente de dos partes: el path, que es la URL con la que debe coincidir la peticin 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 tambin se pasa al controlador, que puede acceder a ellos a travs del nombre asignado en la propia ruta (name en este caso). NOTA El sistema de enrutamiento tiene muchas ms caractersticas para crear URI flexibles y avanzadas en tu aplicacin. Para ms detalles, consulta el captulo 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 cdigo que se ejecuta. El segundo paso del proceso de creacin de pginas consiste precisamente en crear ese controlador. La cadena AcmeHelloBundle:Hello:index es el nombre lgico del controlador, que se traduce como el mtodo 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 ms que un mtodo PHP que t creas y Symfony ejecuta. Aqu es donde el cdigo utiliza la informacin de la peticin 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 mtodo 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 pgina HTML muy simple). Enhorabuena! Despus de crear solamente una ruta y un controlador ya tienes una pgina completamente funcional! Si todo lo has configurado correctamente, la aplicacin debe darte la bienvenida al acceder a la siguiente URL: http://localhost/app_dev.php/hello/Ryan TRUCO Tambin puedes ver tu aplicacin en el entorno prod o de produccin visitando: http://localhost/app.php/hello/Ryan Si se produce un error, probablemente sea porque necesitas vaciar la cach de produccin ejecutando el siguiente comando: php app/console cache:clear --env=prod --no-debug El tercer y ltimo paso para crear una pgina es opcional pero casi todas las aplicaciones lo hacen: crear una plantilla. NOTA Los controladores son el punto de entrada principal a tu cdigo y la clave en la creacin de pginas. Puedes encontrar mucha ms informacin en el captulo 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 cdigo HTML) a un archivo separado y reutilizar diferentes partes del diseo de la pgina. En vez de escribir el cdigo HTML dentro del controlador, genera el cdigo 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', arra y('name' => $name)); } } NOTA Para poder usar el mtodo render(), tu controlador debe extender de la claseSymfony\Bundle\FrameworkBundle\Controller\Controller, la cual aade atajos para las tareas ms comunes de los controladores. Esto se hace en el ejemplo anterior aadiendo la declaracin usedebajo del namespace y luego aadiendo el extends Controller a la clase. El mtodo render() crea un objeto Response y le aade el contenido resultante de renderizar la plantilla. As que como cualquier otro controlador, el cdigo 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 clsicas plantillas creadas con PHP y las nuevas y concisas plantillas creadas con Twig. No te asustes porque puedes elegir libremente cul utilizar o incluso mezclar las dos en el mismo proyecto. Al procesar la plantilla AcmeHelloBundle:Hello:index.html.twig, el controlador utiliza la siguiente convencin de nomenclatura: NombreBundle:NombreControlador:NombrePlantilla Este es el nombre lgico de la plantilla, que se traduce a un archivo fsico utilizando la siguiente convencin: </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 situacin a travs de la plantilla Twig lnea por lnea: La etiqueta extends indica que se utiliza una plantilla padre donde se define el diseo del sitio web. La etiqueta block indica que todo su contenido se debe colocar dentro de un bloque llamado body. Como se explicar ms adelante, la plantilla padre (base.html.twig) es la responsable de definir ese bloque y de mostrarlo en la pgina 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 ningn 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('favi con.ico') ?>" /> </head> <body> <?php $view['slots']->output('_content') ?> <?php $view['slots']->output('stylesheets') ?> </body> </html> El archivo de la plantilla base define el diseo HTML y muestra el contenido del bloque body que se defini en la plantilla index.html.twig. Tambin 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 pginas de la aplicacin. Una plantilla puede generar cualquier cosa, desde el cdigo HTML o CSS hasta cualquier otra cosa que el controlador tenga que devolver. Dentro del ciclo peticin/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. 4.3. La estructura de directorios Las secciones anteriores explican la filosofa en la que se basa la creacin y procesamiento de pginas en Symfony2. Tambin se ha mencionado cmo estn estructurados y organizados los proyectos Symfony2. Al final de esta seccin, sabrs dnde encontrar y colocar diferentes tipos de archivos y por qu. Aunque se puede cambiar, por defecto todas las aplicaciones Symfony tienen la misma estructura de directorios sencilla (y recomendada): app/: contiene la configuracin de la aplicacin. src/: aqu se encuentra todo el cdigo PHP de la aplicacin. vendor/: por convencin aqu se guardan todas las libreras creadas por terceros. web/: este es el directorio web raz y contiene todos los archivos que se pueden acceder pblicamente. 4.3.1. El directorio web El directorio web raz es el lugar donde se encuentran todos los archivos pblicos y estticos tales como imgenes, hojas de estilo y archivos JavaScript. Tambin es el lugar donde se definen todos los controladores frontales, como por ejemplo el siguiente: // web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('prod', false); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send(); El archivo del controlador frontal (app.php en este ejemplo) es el archivo PHP que realmente se ejecuta cuando utilizas una aplicacin Symfony2 y su trabajo consiste en arrancar la aplicacinutilizando una clase del ncleo (AppKernel). TRUCO Tener un controlador frontal significa que se utilizan URL diferentes y ms flexibles que las de una aplicacin PHP tpica. Cuando se dispone de un controlador frontal, las URL se formatean de la siguiente manera: http://localhost/app.php/hello/Ryan El controlador frontal, app.php, se ejecuta y la URL interna: /hello/Ryan se dirige internamente segn la configuracin de enrutamiento. Si adems utilizas el mdulo mod_rewrite de Apache, puedes forzar la ejecucin del archivo app.phpsin necesidad de incluirlo en la URL, por lo que as las URL son todava ms limpias: http://localhost/hello/Ryan Aunque los controladores frontales son esenciales para servir cada peticin, rara vez los tendrs que modificar o incluso pensar en ellos. Los mencionaremos brevemente de nuevo en la seccin dedicada a los entornos de ejecucin. 4.3.2. El directorio de la aplicacin (app) Como vimos en el controlador frontal, la clase AppKernel es el punto de entrada principal de la aplicacin y es la responsable de toda la configuracin. Como tal, se almacena en el directorio app/. Esta clase debe implementar dos mtodos que definen todo lo que Symfony necesita saber acerca de tu aplicacin. Ni siquiera tienes que preocuparte de estos mtodos durante el arranque Symfony los rellena por ti con parmetros predeterminados. registerBundles(): devuelve un array con todos los bundles necesarios para ejecutar la aplicacin. registerContainerConfiguration(): carga el archivo de configuracin de recursos de la aplicacin (consulta la seccin Configurando la aplicacin). Durante el desarrollo de una aplicacin, normalmente el directorio app/ solo los utilizas para modificar la configuracin y los archivos de enrutamiento en el directorio app/config/ (consulta la seccinConfigurando la aplicacin). Este directorio tambin contiene el directorio cach de la aplicacin (app/cache), un directorio de logs(app/logs) y un directorio para archivos de recursos globales, tales como plantillas (app/Resources). Aprenders ms sobre cada uno de estos directorios en captulos posteriores. Carga automticaAl arrancar Symfony, se incluye un archivo especial llamado vendor/autoload.php. Este archivo, creado por Composer, se encarga de configurar el cargador automtico de clases, que a su vez carga automticamente todos los archivos de tu aplicacin que se encuentren en el directorio src/ y todas las libreras externas configuradas en el archivo composer.json. Gracias al cargador automtico, nunca tendrs que preocuparte de usar declaraciones include orequire. Esto es posible porque Composer utiliza namespace o espacio de nombres de una clase para determinar su ubicacin y as incluye automticamente el archivo en el instante en que necesitas una clase. El cargador automtico ya est configurado para buscar cualquiera de tus clases PHP en el directorio src/. Para que funcione la carga automtica, el nombre de la clase y la ruta del archivo deben seguir el mismo patrn: Nombre de la clase: Acme\HelloBundle\Controller\HelloController Ruta fsica del archivo: src/Acme/HelloBundle/Controller/HelloController.php 4.3.3. El directorio fuente (src) En pocas palabras, el directorio src/ contiene todo el cdigo real (cdigo PHP, plantillas, archivos de configuracin, estilos, etc.) que pertenece a tu aplicacin. De hecho, al programar una aplicacin Symfony, la mayor parte de tu trabajo se llevar a cabo dentro de uno o ms bundles creados en este directorio. Pero, qu es exactamente un bundle? 4.4. El sistema de bundles Un bundle es un concepto similar al de los plugins en otras aplicaciones, pero todava mejor. La diferencia clave es que en Symfony2 todo es un bundle, incluyendo tanto la funcionalidad bsica de la plataforma como el cdigo escrito para tu aplicacin. Los bundles son la parte ms importante de Symfony2. Permiten utilizar funcionalidades construidas por terceros o empaquetar tus propias funcionalidades para distribuirlas y reutilizarlas en otros proyectos. Adems, facilitan mucho la activacin o desactivacin de determinadas caractersticas dentro de una aplicacin. NOTA En esta seccin solo se va a explicar lo bsico, pero hay un captulo dedicado completamente al tema de los bundles. Un bundle simplemente es un conjunto estructurado de archivos que se encuentran en un directorio y que implementan una sola caracterstica. Puedes crear por ejemplo un BlogBundle, un ForoBundle o un bundle para gestionar usuarios (muchos de ellos ya existen como bundles de software libre). Cada directorio contiene todo lo relacionado con esa caracterstica, incluyendo archivos PHP, plantillas, hojas de estilo, archivos Javascript, tests y cualquier otra cosa necesaria. Las aplicaciones Symfony se componen de bundles, tal como se define en el mtodoregisterBundles() de la clase AppKernel: // app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), );
if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle( ); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistribution Bundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle (); }
return $bundles; } Con el mtodo registerBundles(), puedes controlar completamente los bundles que utiliza tu aplicacin, incluso aquellos bundles que forman el ncleo del framework. TRUCO Un bundle se puede encontrar en cualquier directorio, siempre y cuando Symfony2 lo pueda cargar automticamente (con el autocargador configurado en app/autoload.php). 4.4.1. Creando un bundle La edicin estndar de Symfony incluye un comando para crear bundles totalmente funcionales de una manera muy sencilla. Por supuesto, tambin puedes crear los bundles a mano si lo prefieres. Para mostrarte lo sencillo que es el sistema de bundles, vamos a crear a mano y activar un nuevo bundle llamado AcmeTestBundle. TRUCO La parte Acme es slo un nombre ficticio que debes sustituir por el nombre del proveedor del bundle, es decir, el nombre o siglas de tu empresa (por ejemplo, ABCTestBundle si la empresa se llama ABC). Si es un bundle personal, puedes utilizar tu nombre completo o las iniciales. En primer lugar, crea un directorio src/Acme/TestBundle/ y aade un nuevo archivo llamadoAcmeTestBundle.php:: // src/Acme/TestBundle/AcmeTestBundle.php namespace Acme\TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeTestBundle extends Bundle { } TRUCO El nombre AcmeTestBundle sigue las convenciones estndar de nomenclatura de bundles. Tambin puedes optar por acortar el nombre del bundle simplemente a TestBundle al nombrar esta clase TestBundle (y el nombre del archivo TestBundle.php). Esta clase vaca es lo nico que necesitamos para crear nuestro nuevo bundle. Aunque normalmente est vaca, esta clase te permite personalizar el comportamiento del bundle. Ahora que hemos creado nuestro bundle, tenemos que activarlo a travs de la clase AppKernel: // app/AppKernel.php public function registerBundles() { $bundles = array( ...,
// registra tus bundles new Acme\TestBundle\AcmeTestBundle(), ); // ...
return $bundles; } Aunque todava no hace nada, el bundle AcmeTestBundle ya est listo para utilizarlo. Generar un bundle a mano es bastante sencillo, pero Symfony tambin proporciona un comando para generar la estructura bsica del bundle: php app/console generate:bundle --namespace=Acme/TestBundle Como resultado este comando genera el esqueleto del bundle formado por un controlador bsico, la plantilla y la configuracin del enrutamiento. Ms adelante se explica en detalle la consola de comandos de Symfony2. TRUCO Cuando crees un nuevo bundle o uses un bundle de terceros, asegrate siempre de habilitar el bundle en el mtodo registerBundles(). El comando generate:bundle se encarga de hacerlo automticamente. 4.4.2. Estructura de directorios de un bundle La estructura de directorios de un bundle es simple y flexible. Por defecto el sistema de bundles sigue una serie de convenciones que ayudan a mantener el cdigo consistente entre todos los bundles Symfony2. Echa un vistazo a AcmeHelloBundle, ya que contiene algunos de los elementos ms comunes de un bundle: Controller/: contiene los controladores del bundle (por ejemplo, HelloController.php). DependencyInjection/: contiene elementos relacionados con el contenedor de inyeccin de dependencias, un concepto muy avanzado que se explicar ms adelante. Entre otras, contiene las extensiones para las clases de inyeccin de dependencias, la configuracin que importan los servicios y registra uno o ms pases del compilador (este directorio no es obligatorio). Resources/config/: contiene la configuracin, incluyendo la configuracin de enrutamiento (por ejemplo, routing.yml). Resources/views/: contiene las plantillas organizadas segn el nombre del controlador (por ejemplo, Hello/index.html.twig). Resources/public/: contiene recursos web (imgenes, hojas de estilo, etc.) y es copiado o enlazado simblicamente al directorio web/ del proyecto con el comando assets:install. Tests/: tiene los tests unitarios y funcionales del bundle. Un bundle puede ser tan pequeo o tan grande como sea necesario. A medida que avances en el libro, aprenders cmo persistir objetos a una base de datos, crear y validar formularios, crear traducciones para tu aplicacin, escribir tests y mucho ms. Cada uno de estos tiene su propio lugar y rol dentro del bundle. 4.5. Configurando la aplicacin La aplicacin est formada por una coleccin de bundles que representan todas las caractersticas y capacidades de tu aplicacin. Cada bundle se puede personalizar a travs de archivos de configuracin escritos en YAML, XML o PHP. De forma predeterminada, el archivo de configuracin principal se encuentra en el directorio app/config/ y se llama config.yml, config.xml o config.php en funcin del formato que prefieras: YAML XML PHP # app/config/config.yml imports: - { resource: parameters.yml } - { resource: security.yml }
# ... NOTA Aprenders cmo cargar cada archivo y formato en la siguiente seccin dedicada a los entornos de ejecucin. Cada opcin de nivel superior como framework o twig define la configuracin de un bundle especfico. Por ejemplo, la clave framework define la configuracin para el ncleo de Symfony FrameworkBundle e incluye la configuracin de enrutamiento, plantillas, y otros elementos del ncleo. No te preocupes por el momento de las opciones de configuracin especficas de cada seccin, ya que el archivo de configuracin viene con parmetros predeterminados. A medida que leas y explores ms cada parte de Symfony2, aprenders sobre las opciones de configuracin especficas de cada caracterstica. Formatos de configuracinA lo largo de los captulos, todos los ejemplos de configuracin muestran los tres formatos (YAML, XML y PHP). Cada uno tiene sus propias ventajas y desventajas. T eliges cual utilizar: YAML: sencillo, limpio y fcil de leer. XML: ms poderoso que YAML y permite el autocompletado del IDE. PHP: el ms potente, pero menos fcil de leer que los formatos de configuracin estndar. 4.5.1. Obteniendo la configuracin por defecto El nuevo comando config:dump-reference te permite volcar a la consola toda la configuracin por defecto de un bundle en formato YAML. El siguiente ejemplo muestra cmo volcar la configuracin delbundle FrameworkBundle: $ app/console config:dump-reference FrameworkBundle En vez de el nombre completo del bundle, tambin puedes utilizar el nombre de su opcin de configuracin principal: $ app/console config:dump-reference framework 4.6. Entornos Una aplicacin puede funcionar en diversos entornos. Los diferentes entornos comparten el mismo cdigo PHP (solo es diferente el controlador frontal), pero usan una configuracin diferente. Por ejemplo, un entorno de desarrollo dev guarda las advertencias y errores, mientras que un entorno de produccin prod slo registra los errores. Algunos archivos se vuelven a generar en cada peticin en el entorno dev (para mayor comodidad de los desarrolladores), pero se cachean en el entorno prod. Todos los entornos se encuentran en la misma mquina y ejecutan la misma aplicacin. Un proyecto Symfony2 normalmente comienza con tres entornos (dev, test y prod), aunque resulta sencillo crear nuevos entornos. Puedes ver tu aplicacin en diferentes entornos con slo cambiar el controlador frontal en tu navegador. Para ver la aplicacin en el entorno dev, accede a la aplicacin a travs del controlador frontal de desarrollo: http://localhost/app_dev.php/hello/Ryan Si deseas ver cmo se comportar tu aplicacin en el entorno de produccin, utiliza en su lugar el controlador frontal prod: http://localhost/app.php/hello/Ryan Como el entorno prod est optimizado para ser muy rpido, la configuracin, el enrutamiento y las plantillas Twig se compilan en clases PHP simples y se guardan en cach. Si haces cualquier cambio en las plantillas, no lo vers en el entorno prod a menos que borres la cache de la aplicacin y as fuerces a Symfony a volver a compilar las plantillas. Para borrar la cache del entorno de produccin, ejecuta el siguiente comando de consola: php app/console cache:clear --env=prod --no-debug NOTA Si abres el archivo web/app.php, vers que est configurado explcitamente para usar el entorno prod: $kernel = new AppKernel('prod', false); Puedes crear un nuevo controlador frontal para un nuevo entorno copiando el archivo y cambiandoprod por algn otro valor. NOTA El entorno test se utiliza cuando se ejecutan tests automticas y no se puede acceder directamente a travs del navegador. Consulta el captulo dedicado a los tests para obtener ms informacin. 4.6.1. Configurando entornos La clase AppKernel es realmente la responsable de cargar el archivo de configuracin segn el controlador que utilices: // app/AppKernel.php public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); } Ya sabes que la extensin .yml se puede cambiar a .xml o .php si prefieres usar XML o PHP para escribir tu configuracin. Adems, observa que cada entorno carga su propio archivo de configuracin. Considera el archivo de configuracin para el entorno dev. YAML XML PHP # app/config/config_dev.yml imports: - { resource: config.yml }
# ... La clave imports es similar a una declaracin include de PHP y garantiza que en primer lugar se carga el archivo de configuracin principal (config.yml). El resto del archivo de configuracin predeterminado simplemente aumenta el nmero de eventos que se registran en el log y realiza otros ajustes tiles para un entorno de desarrollo. Ambos entornos (prod y test) siguen el mismo modelo: cada uno importa el archivo de configuracin bsico y luego modifica sus valores de configuracin para adaptarlos a las necesidades especficas del entorno. Esto es slo una convencin, pero te permite reutilizar la mayor parte de tu configuracin y personalizar slo algunas opciones especficas. 4.7. Resumen Enhorabuena! Ahora ya conoces todos los aspectos fundamentales de Symfony2 y has descubierto lo fcil y flexible que puede ser. Aunque todava faltan muchas caractersticas por explicar, asegrate de haber entendido bien los siguientes puntos bsicos: La creacin de una pgina es un proceso de tres pasos que requiere de una ruta, uncontrolador y (opcionalmente) una plantilla. Cada proyecto contiene slo unos cuantos directorios principales: web/ (recursos web y controladores frontales), app/ (configuracin), src/ (tus bundles), y vendor/ (cdigo de terceros). Tambin existe un directorio bin/ que se utiliza para ayudarte a actualizar las libreras de terceros. Cada caracterstica de Symfony2 (incluyendo su propio ncleo) est desarrollada con un bundle, que es un conjunto estructurado de archivos para esa caracterstica. La configuracin de cada bundle se encuentra en el directorio app/config y se puede escribir en YAML, XML o PHP. Cada entorno es accesible a travs de un controlador frontal diferente (por ejemplo, app.php yapp_dev.php) el cual a su vez carga un archivo de configuracin diferente. A partir de aqu, cada captulo explica conceptos ms avanzados de Symfony y algunas de sus herramientas ms poderosas. Cuanto ms sepas sobre Symfony2, ms apreciars la flexibilidad de su arquitectura y el poder que te proporciona para desarrollar aplicaciones rpidamente. Captulo 5. El controlador Un controlador es una funcin PHP creada por ti y que se encarga de obtener la informacin de la peticin HTTP y de generar y devolver la respuesta HTTP (en forma de objeto de tipo Response de Symfony2). La respuesta puede ser una pgina HTML, un documento XML, un array JSON serializado, una imagen, una redireccin a otra pgina, un error de tipo 404 o cualquier otra cosa que se te ocurra. El controlador contiene toda la lgica que tu aplicacin necesita para generar el contenido de la pgina. Para ver lo sencillo que es esto, echemos un vistazo a un controlador real de Symfony2. El siguiente controlador genera una pgina que simplemente imprime Hello world!: use Symfony\Component\HttpFoundation\Response;
public function helloAction() { return new Response('Hello world!'); } El objetivo de un controlador siempre es el mismo: crear y devolver un objeto Response. Para ello, a veces obtiene informacin de la peticin, o busca un recurso en la base de datos, enva un correo electrnico, o guarda informacin en la sesin del usuario. Independientemente de lo que haga, el controlador siempre devuelve un objeto Response que se utiliza para generar la respuesta que se enva al usuario. Como ves ni Symfony se comporta de forma automgica ni existe ms requisitos de los cuales preocuparse. Estos son algunos ejemplos de casos comunes: Controlador A prepara un objeto Response que genera el contenido HTML de la portada del sitio. Controlador B lee el parmetro slug de la peticin para cargar una entrada del blog desde la base de datos y crea un objeto Response para mostrar ese blog. Si el slug no se puede encontrar en la base de datos, crea y devuelve un objeto Response con un cdigo de estado de error 404. Controlador C procesa la informacin enviada en un formulario de contacto: lee la informacin del formulario desde la peticin, guarda la informacin del contacto en la base de datos y enva mensajes de correo electrnico con la informacin de contacto al administrador del sitio web. Por ltimo, crea un objeto Response que redirige el navegador del cliente desde el formulario de contacto a la pgina de agradecimiento. 5.1. Ciclo de vida de la peticin, controlador, respuesta Cada peticin manejada por un proyecto Symfony2 pasa por el mismo ciclo de vida bsico. La plataforma se encarga de todas las tareas repetitivas iniciales y despus, pasa la ejecucin al controlador, que contiene el cdigo personalizado de tu aplicacin: 1. Cada peticin es manejada por un nico archivo controlador frontal (por ejemplo, app.php oapp_dev.php) el cual es responsable de iniciar la aplicacin. 2. El sistema de enrutamiento (clase Routing) lee la informacin de la peticin (por ejemplo, la URI), encuentra una ruta que coincida con esa informacin, y lee el parmetro _controller de la ruta. 3. Se ejecuta el controlador asignado a la ruta y este controlador crea y devuelve un objetoResponse. 4. Las cabeceras HTTP y el contenido del objeto Response se envan de vuelta al cliente. Crear una pgina es tan fcil como definir un controlador (paso 3) y hacer una ruta que asocie una URI con ese controlador (paso 2). NOTA Aunque tienen nombres parecidos, un "controlador frontal" es muy diferente de los"controladores" que se explican en este captulo. Un controlador frontal es un archivo PHP, normalmente pequeo, que se encuentra en el directorio web raz del proyecto y a travs del cual se atienden todas las peticiones del usuario. Una aplicacin tpica tiene un controlador frontal de produccin (por ejemplo, app.php) y un controlador frontal de desarrollo (por ejemplo, app_dev.php). Probablemente nunca necesites editar, ver o preocuparte por los controladores frontales en tu aplicacin. 5.2. Un controlador sencillo A pesar de que un controlador puede ser cualquier cdigo ejecutable PHP (una funcin, un mtodo en un objeto o un Closure), en Symfony2 un controlador suele ser un mtodo dentro de un objeto de tipo controlador. Los controladores tambin se conocen como acciones. // src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller; use Symfony\Component\HttpFoundation\Response;
class HelloController { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } } TRUCO Ten en cuenta que el controlador es el mtodo indexAction, que se encuentra dentro de una "clase controlador" (HelloController). No te dejes confundir por la nomenclatura: una clase controlador simplemente es una forma cmoda de agrupar varios controladores/acciones relacionados. Generalmente, la clase controlador contiene varios controladores (por ejemplo,updateAction, deleteAction, etc.). Este controlador es bastante sencillo, pero vamos a analizarlo lnea por lnea: Lnea 3: Symfony2 utiliza los namespaces de PHP 5.3 para asignar un "espacio de nombres"completo a la clase del controlador. La palabra clave use importa la clase Response, que la misma que nuestro controlador debe devolver. Lnea 6: el nombre de clase es la concatenacin del nombre de tu clase controlador (es decirHello) y la palabra Controller. Esta es una convencin que proporciona consistencia a los controladores y permite hacer referencia slo a la primera parte del nombre (es decir, Hello) en la configuracin del enrutador. Lnea 8: cada accin en una clase controlador se sufija con Action, pero en la configuracin de enrutado se utiliza solo el nombre corto de la accin (index). En la siguiente seccin, se crea una ruta asociada a esta accin. As se ver cmo las partes variables de la ruta (por ejemplo, {name}) se convierten en argumentos para el mtodo de la accin (por ejemplo, $name). Lnea 10: el controlador crea y devuelve un objeto Response. 5.3. Asociando una URI a un controlador El nuevo controlador devuelve una pgina HTML simple. Para poder probar realmente esta pgina en tu navegador, debes crear una ruta cuyo path sea la URI que quieres asociar al controlador: YAML XML PHP # app/config/routing.yml hello: path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } Ahora, al acceder a la URI /hello/ryan se ejecuta el controlador HelloController::indexAction() y se pasa el valor ryan como una variable llamada $name. De nuevo, crear una pgina significa simplemente que debes crear un mtodo controlador y una ruta asociada. Observa la sintaxis utilizada para referirse al controlador: AcmeHelloBundle:Hello:index. Symfony2 utiliza esta notacin corta para referirse a los controladores. Se trata de la sintaxis recomendada y le dice a Symfony2 que busque una clase controlador llamada HelloController dentro de un paquete llamado AcmeHelloBundle y que despus ejecute el mtodo indexAction(). NOTA Este ejemplo define la configuracin de enrutamiento directamente en el directorioapp/config/. Una mejor manera de organizar tus rutas es colocar cada una en el bundle al que pertenece. TRUCO Puedes aprender mucho ms sobre el sistema de enrutado en el captulo de enrutamiento. 5.3.1. Parmetros de la ruta como argumentos del controlador Como ya se explic anteriormente, el valor AcmeHelloBundle:Hello:index del parmetro _controllerse refiere al mtodo HelloController::indexAction() del bundle AcmeHelloBundle. Lo ms interesante son los argumentos que se pasan a ese mtodo: <?php // src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller { public function indexAction($name) { // ... } } El controlador anterior tiene un solo argumento, llamado $name, cuyo valor corresponde al parmetro{name} de la ruta asociada (este valor es ryan en nuestro ejemplo). De hecho, cuando ejecutas tu controlador, Symfony2 asocia cada argumento del controlador con un parmetro de la ruta. Considera el siguiente ejemplo: YAML XML PHP # app/config/routing.yml hello: path: /hello/{firstName}/{lastName} defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } Ahora el controlador admite varios argumentos: public function indexAction($firstName, $lastName, $color) { // ... } Las variables {firstName} y {lastName} de la ruta se llaman placeholders, ya que "guardan el sitio"para que cualquier valor sustituya esa variable. Por otra parte, la variable color es una variable de tipo default, ya que su valor siempre est definido para todas las rutas. Independientemente del tipo de variable, los valores de {first_name}, {last_name} y color estn disponibles en el controlador. Cuando se ejecuta una ruta, tanto los placeholders como las variables por defecto se fusionan en un nico array que se pasa al controlador. Asociar parmetros de la ruta a los argumentos del controlador es bastante fcil y flexible. Pero debes tener en cuenta las siguientes pautas mientras desarrollas. 1. El orden de los argumentos del controlador no importa: Symfony2 es capaz de asociar los nombres de los parmetros de la ruta con los nombres de las variables en la declaracin del mtodo controlador. En otras palabras, se da cuenta de que el parmetro {lastName} coincide con el argumento $lastName. As que puedes cambiar el orden de los argumentos como quieras y todo seguir funcionando bien: public function indexAction($lastName, $color, $firstName) { // ... } 2. Cada argumento obligatorio del controlador debe tener asociado un parmetro en la ruta El siguiente cdigo producir una excepcin de tipo RuntimeException porque no hay ningn parmetro foo definido en la ruta: public function indexAction($firstName, $lastName, $color, $foo) { // ... } Sin embargo, es perfectamente vlido hacer que el argumento sea opcional. El siguiente ejemplo no lanzar una excepcin: public function indexAction($firstName, $lastName, $color, $foo = 'bar') { // ... } 3. No todos los parmetros de la ruta deben ser argumentos en tu controlador Si por ejemplo, lastName no es tan importante para tu controlador, lo puedes omitir por completo: public function indexAction($firstName, $color) { // ... } TRUCO Todas las rutas incluyen adems un parmetro especial llamado _route, que guarda el nombre de la propia ruta (en este ejemplo, hello). Normalmente no se utiliza, pero tambin est disponible como argumento del controlador. 5.3.2. El objeto Request como argumento del controlador Suele ser muy til disponer en el controlador del objeto Request asociado a la peticin del usuario, especialmente cuando trabajas con formularios. Para hacer que Symfony pase este objeto automticamente como argumento del controlador, utiliza el siguiente cdigo: use Symfony\Component\HttpFoundation\Request;
public function updateAction(Request $request) { $form = $this->createForm(...);
$form->handleRequest($request);
// ... } 5.4. La clase base del controlador Symfony2 proporciona una clase Controller base, que contiene varias utilidades para las tareas ms comunes de los controladores y tambin incluye un acceso a cualquier otro recurso que necesite la clase del controlador. Para acceder a todos estos mtodos tiles, haz que la clase que contiene tus acciones herede de la clase Controller. Para ello, aade la siguiente instruccin use al principio de tu clase controlador y luego modifica la declaracin de HelloController para extenderla: // src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response;
class HelloController extends Controller { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } } Este cambio no afecta al funcionamiento del controlador. En la siguiente seccin, se explican loshelpers o mtodos tiles que la clase base del controlador pone a tu disposicin. Estos mtodos slo son atajos para utilizar ms fcilmente las funcionalidades del ncleo de Symfony2 (que tambin se pueden utilizar sin esta clase base, pero no de forma tan cmoda). Una buena manera de aprender en la prctica esas funcionalidades del ncleo es ver el cdigo fuente de esa clase base en el archivoSymfony\Bundle\FrameworkBundle\Controller\Controller. TRUCO Extender la clase base Controller en Symfony es opcional. Aunque muchos programadores lo hacen para utilizar los atajos, no es obligatorio su uso. Si lo prefieres, puedes extender la claseSymfony\Component\DependencyInjection\ContainerAware o utilizar el trait ContainerAwareTrait si tu versin de PHP es 5.4 o superior. En ambos casos, el contenedor de servicios de Symfony estar disponible a travs de la propiedad container. NOTA Tambin puedes definir los controladores como servicios. Definirlos as es opcional, pero si lo haces, tendrs un mayor control de las dependencias que se inyectan en tus controladores. 5.5. Tareas comunes del controlador Aunque un controlador puede hacer prcticamente cualquier cosa, la mayora de los controladores siempre se encargan de las mismas tareas bsicas. Estas tareas, tales como redirigir a otra pgina, procesar plantillas y acceder a servicios bsicos, son muy fciles de manejar en Symfony2. 5.5.1. Redirigiendo Si deseas redirigir al usuario a otra pgina, utiliza el mtodo redirect(): public function indexAction() { return $this->redirect($this->generateUrl('homepage')); } El mtodo generateUrl() es slo una funcin auxiliar que genera la URL de una determinada ruta. Para ms informacin, consulta el captulo de enrutamiento. Por defecto, el mtodo redirect() produce una redireccin temporal de tipo 302. Para realizar una redireccin permanente de tipo 301, modifica el segundo argumento: public function indexAction() { return $this->redirect($this->generateUrl('homepage'), 301); } TRUCO El mtodo redirect() simplemente es un atajo que crea un objeto Response especial que redirige a los usuarios. Su cdigo es equivalente a: use Symfony\Component\HttpFoundation\RedirectResponse;
return new RedirectResponse($this->generateUrl('homepage')); 5.5.2. Reenviando Tambin resulta sencillo realizar redirecciones internas hacia otro controlador de la aplicacin mediante e, mtodo forward(). En lugar de redirigir el navegador del usuario, se hace una nueva peticin interna (en realidad, sub-peticin) y se ejecuta el controlador especificado. El mtodoforward() devuelve el objeto Response generado por el segundo controlador: public function indexAction($name) { $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green' ));
// puedes seguir modificando la respuesta o // devolverla directamente
return $response; } Ten en cuenta que el mtodo forward() utiliza la notacin corta habitual para indicar el controlador que se ejecuta. En este caso, la clase controlador de destino ser HelloController dentro del bundleAcmeHelloBundle. El array que se pasa como segundo parmetro del mtodo se convierte en los argumentos utilizados en el controlador resultante. Esta misma sintaxis se utiliza para ejecutar controladores directamente desde las plantillas, tal y como se explicar ms adelante. El mtodo del controlador destino debe tener un aspecto como el siguiente: public function fancyAction($name, $color) { // ... crea y devuelve un objeto Response } Como sucede con los argumentos de los controladores y las rutas, el orden de los argumentos parafancyAction no tiene la menor importancia. Symfony2 asocia las claves del array (por ejemplo, name) con el nombre del argumento del mtodo (por ejemplo, $name). Si cambias el orden de los argumentos, Symfony2 contina pasando el valor correcto a cada variable. TRUCO Al igual que otros mtodos de la clase base Controller, el mtodo forward slo es un atajo de la funcionalidad del ncleo de Symfony2. Puedes realizar un forward duplicando la peticin actual y ejecutndola mediante el servicio http_kernel, que devuelve un objeto de tipo Response: use Symfony\Component\HttpKernel\HttpKernelInterface;
$httpKernel = $this->container->get('http_kernel'); $response = $httpKernel->handle( $subRequest, HttpKernelInterface::SUB_REQUEST ); 5.5.3. Procesando plantillas Aunque no es obligatorio, la mayora de los controladores acaban su ejecucin renderizando una plantilla para generar el cdigo HTML que se devuelve al usuario. El mtodo renderView() procesa una plantilla y devuelve su contenido renderizado. As que puedes usar fcilmente una plantilla para generar el contenido del objeto Response: use Symfony\Component\HttpFoundation\Response;
return new Response($content); Si lo prefieres, tambin dispones del mtodo render() para hacer lo anterior en un solo paso, ya que crea un objeto Response y le aade como contenido el resultado de renderizar la plantilla: return $this->render( 'AcmeHelloBundle:Hello:index.html.twig', array('name' => $name) ); En ambos casos, la notacin utilizada indica que se renderizar la plantilla que se encuentra en el archivo Resources/views/Hello/index.html.twig del bundle AcmeHelloBundle. El motor de plantillas de Symfony se explica con gran detalle en el captulo dedicado a las plantillas. TRUCO Si lo prefieres, tambin puedes evitar el uso del mtodo render() utilizando la anotacin@Template. Lee la documentacin del FrameworkExtraBundle para obtener ms detalles. TRUCO El mtodo renderView es un atajo para usar el servicio templating, que tambin puedes utilizar directamente con el siguiente cdigo: $templating = $this->get('templating'); $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', arra y('name' => $name)); 5.5.4. Accediendo a otros servicios Al extender la clase base del controlador, puedes acceder a cualquier servicio de Symfony2 a travs del mtodo get(). Estos son algunos de los servicios comunes que puedes necesitar: $templating = $this->get('templating');
$router = $this->get('router');
$mailer = $this->get('mailer'); Symfony dispones de infinidad de servicios ya creados, pero tambin puedes definir tus propios servicios. Para listar todos los servicios disponibles, utiliza el comando container:debug en la consola: $ php app/console container:debug Para ms informacin, consulta el captulo dedicado al contenedor de servicios. 5.6. Gestionando errores y pginas 404 Cuando no se encuentra un recurso, el protocolo HTTP indica que debes devolver un error con cdigo de estado 404. Para ello, debes lanzar en tu cdigo una excepcin especial. Si ests extendiendo la clase base del controlador, haz lo siguiente: public function indexAction() { $product = // recupera el objeto desde la base de datos
if (!$product) { throw $this->createNotFoundException('El producto solicitado no exis te.'); }
return $this->render(...); } El mtodo createNotFoundException() crea un objeto especial de tipo NotFoundHttpException, que a su vez genera una respuesta de tipo 404 en el interior de Symfony. Obviamente puedes lanzar cualquier tipo de excepcin en tu controlador. Symfony2 convierte automticamente las excepciones en respuestas HTTP con cdigo de error 500. throw new \Exception('Algo no ha salido bien.'); En ambos casos, el usuario final ve una pgina de error normal y a los desarrolladores se les muestra una pgina de error con mucha informacin de debug o depuracin (siempre que utilices el entorno de ejecucin de desarrollo). 5.7. Gestionando la sesin Symfony2 incluye un objeto de sesin que permite almacenar informacin persistente sobre el usuario, es decir, informacin que se guarda de una peticin a otra. Por defecto Symfony2 almacena la informacin en una cookie usando las sesiones nativas de PHP. Desde cualquier controlador resulta sencillo almacenar y recuperar informacin de la sesin: use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request) { $session = $request->getSession();
// guarda un atributo para reutilizarlo durante una // peticin posterior del usuario $session->set('foo', 'bar');
// obtener el valor de un atributo de la sesin $foo = $session->get('foo');
// utilizar un valor por defecto si el atributo no existe $filters = $session->get('filters', array()); } Esta informacin se mantendr asociada al usuario mientras no caduque su sesin. 5.7.1. Mensajes flash Este tipo de mensajes se utilizan para almacenar una pequea cantidad de informacin que solo est disponible durante la siguiente peticin (despus se borran automticamente). Estos mensajes son muy tiles cuando procesas por ejemplo un formulario (por el momento no te fijes en los detalles del formulario, solo en cmo crear el mensaje flash): use Symfony\Component\HttpFoundation\Request;
public function updateAction(Request $request) { $form = $this->createForm(...);
$form->handleRequest($request); if ($form->isValid()) {
// cdigo que procesa el formulario ...
$this->get('session')->getFlashBag()->add( 'notice', 'Se han guardado los cambios.' );
return $this->render(...); } Despus de procesar la peticin, el controlador crea un mensaje flash llamado notice y luego redirige al usuario a otra pgina. El nombre del propio mensaje (notice en este caso) no tiene importancia, ya que solo es un identificador nico que utilizas en tu cdigo. En la plantilla asociada a la siguiente peticin, puedes mostrar (si quieres) el contenido de ese mensaje utilizando el siguiente cdigo: TWIG PHP {% for flashMessage in app.session.flashbag.get('notice') %} <div class="flash-notice"> {{ flashMessage }} </div> {% endfor %} Recuerda que los mensajes flash solo estn disponibles durante la siguiente peticin despus de haber sido creados. Tanto si muestras su contenido como si lo ignoras, el mensaje flash se borra automticamente despus de esa peticin. El objetivo de estos mensajes es mostrar algn aviso despus de una redireccin, tal y como se ha mostrado en el ejemplo anterior. 5.8. El objeto Response El nico requisito para un controlador es que devuelva un objeto de tipo Response. La claseSymfony\Component\HttpFoundation\Response es una abstraccin en PHP de la verdadera respuesta HTTP (que est formada por el contenido que se enva al usuario y las cabeceras HTTP adecuadas): // crea una respuesta simple con un cdigo de estado // igual a 200 (el predeterminado) $response = new Response('Hello '.$name, Response::HTTP_OK);
// crea una respuesta JSON con cdigo de estado 200 $response = new Response(json_encode(array('name' => $name))); $response->headers->set('Content-Type', 'application/json'); TRUCO La propiedad headers es un objeto de tipo Symfony\Component\HttpFoundation\HeaderBag, que contiene varios mtodos tiles para lectura y modificacin de las cabeceras del objetoResponse. Los nombres de las cabeceras se normalizan, para que no importe si utilizas Content- Type, content-type o incluso content_type. [tip] Tambin existen varias clases especiales para simplificar algunos tipos de respuesta comunes: Para devolver respuestas en formato JSON, utiliza la clase JsonResponse. Para servir archivos binarios, utiliza la clase BinaryFileResponse. [/tip] 5.9. El objeto Request Adems de las variables definidas por la ruta, el controlador tiene acceso directo al objeto Request. El motivo es que Symfony inyecta automticamente el objeto Request si el controlador define un argumento de tipo Symfony\Component\HttpFoundation\Request (no importa ni el nombre del argumento ni su posicin): use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request) { // es una peticin Ajax? $request->isXmlHttpRequest();
// obtiene el valor de un parmetro $_GET $request->query->get('page');
// obtiene el valor de un parmetro $_POST $request->request->get('page'); } Al igual que el objeto Response, las cabeceras de la peticin se almacenan en un objeto HeaderBag y son fcilmente accesibles. 5.10. Resumen Siempre que creas una pgina, tienes que escribir el cdigo PHP que contiene la lgica asociada a esa pgina. En Symfony, a esto se le llama controlador, y es una funcin PHP que puede hacer cualquier cosa que necesites siempre que al final devuelvas un objeto de tipo Response con el que Symfony2 construye la respuesta enviada al usuario. Para que tu trabajo de programador sea ms sencillo, puedes optar por extender la clase baseController, que contiene atajos para muchas de las tareas ms comunes de los controladores. Uno de los ms utilizados es el mtodo render(), que renderiza las plantillas que generan el cdigo HTML devuelto al usuario. En los prximos captulos se explicar cmo utilizar el controlador para guardar y recuperar objetos desde una base de datos, procesar formularios, manejar la cach y mucho ms. Captulo 6. El enrutamiento Las URL limpias son absolutamente imprescindibles en cualquier aplicacin web que se precie. Deberas olvidarte para siempre de las URL feas como index.php?article_id=57 y utilizar en su lugar URL como /leer/intro-a-symfony. Tambin es importante contar con cierta flexibilidad para cambiar las URL. Qu pasa si necesitas cambiar la URL de una pgina de /blog a /noticias? Cunto tiempo te costara buscar los enlaces que apuntan a /blog y modificarlos por la nueva URL? Si utilizas el enrutador de Symfony, el cambio sera instantneo y muy sencillo. El enrutador de Symfony2 te permite asociar URL a diferentes reas de la aplicacin. Despus de leer este captulo, sers capaz de: Crear rutas complejas y asociarlas a controladores. Generar URL dentro de las plantillas y los controladores. Cargar rutas desde otros bundles o archivos externos. Depurar las rutas de la aplicacin. 6.1. El enrutamiento en la prctica Una ruta es una asociacin entre un patrn de URL y un controlador. Supongamos por ejemplo que deseas asociar URL de tipo /blog/mi-post o /blog/todo-sobre- symfony con un controlador que sea capaz de buscar y mostrar el artculo solicitado. La ruta necesaria para ello es muy simple: YAML XML PHP # app/config/routing.yml blog_show: path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } NOTA La opcin path solamente est definida desde la versin 2.2 de Symfony. En las versiones anteriores esta opcin se llamaba pattern. El path definido por la ruta blog_show se interpreta realmente como /blog/*, es decir, la cadena de texto /blog/ seguida de cualquier otro contenido. El valor de ese contenido se guarda en una variable llamada slug. Para la URL /blog/mi- post, la variable slug vale mi-post. Esta variable est disponible directamente en el controlador, tal y como se explicar ms adelante. El parmetro _controller es una opcin especial que le dice a Symfony qu controlador se debe ejecutar cuando la URL solicitada por el usuario coincide con el patrn de la ruta. El controlador se indica a travs de su nombre lgico, que es una notacin especial para hacer referencia a un mtodo concreto de una clase concreta de PHP: // src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller { public function showAction($slug) { $blog = // usa la variable $slug para consultar la base de datos
return $this->render('AcmeBlogBundle:Blog:show.html.twig', array( 'blog' => $blog, )); } } Enhorabuena! Acabas de crear tu primera ruta y la has asociado a un controlador. Ahora, cuando visites /blog/mi-post, se ejecutar el controlador showAction y se le pasar una variable llamada$slug cuyo valor ser mi-post. Este es el objetivo del enrutador de Symfony2: asociar la URL de una peticin a un controlador. En el resto del captulo vas a aprender todo tipo de trucos que te facilitarn el trabajo incluso con las URL ms complejas. 6.2. Funcionamiento interno del enrutamiento Cuando el usuario realiza una peticin a tu aplicacin, esta contiene la direccin exacta del recurso que solicita el usuario. Esta direccin se conoce como URL (o URI), y podra ser /contacto,/blog/read-me, o cualquier otra cosa. Considera la siguiente peticin HTTP de ejemplo: GET /blog/my-blog-post El objetivo del sistema de enrutado de Symfony2 es analizar esta URL y determinar qu controlador se debe ejecutar. El proceso completo consta de los siguientes pasos: 1. La peticin se procesa en el controlador frontal de Symfony2 (por ejemplo, en el archivo app.php). 2. El ncleo de Symfony2 (conocido como kernel) solicita al enrutador que examine la peticin. 3. El enrutador busca qu patrn de las rutas de la aplicacin coincide con la URL entrante y devuelve informacin sobre la ruta, incluyendo el controlador que se debe ejecutar. 4. El ncleo de Symfony2 ejecuta el controlador, que en ltima instancia, devuelve un objetoResponse.
Figura 6.1 Flujo de la peticin en Symfony2 6.3. Creando rutas Symfony carga todas las rutas de tu aplicacin desde un archivo de configuracin de enrutamiento. Normalmente este archivo es app/config/routing.yml, pero puedes utilizar cualquier otro archivo e incluso otros formatos de configuracin como XML o PHP. Para ello, cambiar el valor de la siguiente opcin de configuracin: YAML XML PHP # app/config/config.yml framework: # ... router: { resource: "%kernel.root_dir%/config/routing.yml" } TRUCO Aunque todas las rutas se cargan desde un solo archivo, es muy comn importar otros archivos desde ese archivo principal de configuracin de enrutamiento. Ms adelante en este mismo captulo se explica cmo importar otros archivos. 6.3.1. Configuracin bsica de rutas Las aplicaciones tpicas definen decenas de rutas. Afortunadamente, definir una ruta es bastante fcil. Una ruta bsica consta de dos partes: el path o patrn que debe cumplir la URL y un array de opciones llamado defaults: YAML XML PHP _welcome: path: / defaults: { _controller: AcmeDemoBundle:Main:homepage } Esta ruta corresponde a la portada del sitio (por eso el valor / en el patrn) y est asociada con el controlador AcmeDemoBundle:Main:homepage. Symfony2 utiliza el valor de la opcin _controller para determinar la funcin PHP que se ejecuta para responder a la peticin. La transformacin de_controller en una funcin PHP se explica ms adelante. 6.3.2. Rutas con variables El sistema de enrutamiento ofrece unas posibilidades mucho ms interesantes que las de la seccin anterior. Muchas rutas contienen una o ms variables, tambin llamados placeholders: YAML XML PHP blog_show: path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } El patrn de esta ruta se cumple para cualquier URL que empiece por /blog/ y despus contenga cualquier valor. Ese valor variable se almacena en una variable llamada slug (sin las llaves { y }) y se pasa al controlador. En otras palabras, si la URL es /blog/hello-world, el controlador dispone de una variable $slug cuyo valor es hello-world. Esta variable es la que utilizar por ejemplo el controlador para buscar en la base de datos el artculo solicitado. Si pruebas a acceder a la URL /blog, vers que Symfony2 no ejecuta el controlador de esta ruta. El motivo es que por defecto todas las variables de las rutas deben tener un valor. La solucin consiste en asignar un valor por defecto a determinadas variables de la ruta mediante el array defaults. 6.3.3. Variables obligatorias y opcionales Vamos a complicar un poco el ejemplo de la seccin anterior y para ello, se va a aadir una nueva ruta llamada blog que muestra un listado de todos los artculos del blog YAML XML PHP blog: path: /blog defaults: { _controller: AcmeBlogBundle:Blog:index } Por el momento esta ruta es muy simple, ya que no incluye ninguna variable y por tanto, solo se activar cuando el usuario solicite exactamente la URL /blog. Pero, qu sucede si necesitamos que esta ruta sea compatible con la paginacin, donde la URL /blog/2 muestra la segunda pgina de los artculos del blog? Modifica la ruta anterior para que incluya una nueva variable llamada {page}: YAML XML PHP blog: path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index } Al igual que la variable {slug} del ejemplo anterior, el valor asociado a {page} ahora estar disponible dentro del controlador. As que ya puedes utilizar su valor para saber qu artculos debes mostrar en una determinada pgina. El problema es que, como por defecto las variables de las rutas son obligatorias, esta ruta ya no funciona cuando un usuario solicita la URL /blog. As que para ver la portada del blog, el usuario tendrs que utilizar la URL /blog/1. Obligar al usuario a recordar que siempre debe entrar en la primera pgina es simplemente ridculo. As que vamos a modificar la ruta para que la variable {page}sea opcional. Para ello, asigna un valor por defecto a la variable dentro del array defaults: YAML XML PHP blog: path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } Como la variable page ya dispone de un valor por defecto dentro de defaults, ya no es necesario indicarla siempre en la URL. As que cuando el usuario solicite la URL /blog, esta ruta s que se ejecutar y se asignar automticamente el valor 1 a la variable page. Si se solicita la URL /blog/2, la ruta tambin se ejecuta y el valor de la variable page ser 2, tal y como se indica en la propia ruta. Resumiendo: URL Ruta Parmetros /blog blog {page} = 1 /blog/1 blog {page} = 1 URL Ruta Parmetros /blog/2 blog {page} = 2 ADVERTENCIA Tambin es posible definir ms de una variable opcional en la ruta (por ejemplo:/blog/{slug}/{page}). La nica restriccin es que todo lo que va despus de una variable opcional tambin tiene que ser opcional. As, para la ruta /{page}/blog la variable page siempre ser obligatoria (/blog no es una URL vlida para esta ruta). TRUCO Las rutas con variables opcionales al final no son vlidas cuando la URL solicitada por el usuario tiene una barra / al final. As que la URL /blog/ no ser vlida para esta ruta, pero s lo ser la URL /blog). 6.3.4. Agregando requisitos Observa las rutas definidas en los ejemplos de las secciones anteriores: YAML XML PHP blog: path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
blog_show: path: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show } Has visto cul es el problema? Las dos rutas tienen el mismo patrn, por lo que las dos podran responder a las URL que empiecen por /blog/ y continuen con cualquier cosa. En estas situaciones, el sistema de enrutamiento de Symfony siempre elige la ruta que se ha definido primero. As que la ruta blog_show jams se ejecutar. Adems, URL como /blog/mi-post harn que se ejecute la primera ruta (blog), lo que provocar un error, ya que no tiene sentido que la variable page valga mi-post. URL Ruta Parmetros /blog/2 blog {page} = 2 /blog/mi-post blog {page} = mi-post La solucin a este problema consiste en aadir requisitos o condiciones a la ruta. En este caso, si el patrn /blog/{page} slo funcionara cuando {page} es un nmero entero, las dos rutas funcionaran sin problemas. Afortunadamente, es posible aadir requisitos a cada variable de la ruta mediante expresiones regulares: YAML XML PHP blog: path: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } requirements: page: \d+ El requisito \d+ es una expresin regular que obliga a que el valor del parmetro {page} est formado exclusivamente por dgitos (es decir, que sea un nmero entero y positivo). Despus de este cambio, la ruta blog seguir funcionando para URL como /blog/2 (ya que 2 es un nmero), pero ya no se ejecutar para URL como /blog/mi-post (porque mi-post no es un nmero). Por tanto, ahora la URL/blog/mi-post se procesa mediante la ruta blog_show. URL Ruta Parmetros /blog/2 blog {page} = 2 /blog/mi-post blog_show {slug} = mi-post /blog/2-mi-post blog_show {slug} = 2-mi-post Las primeras rutas siempre gananTodo lo anterior significa que el orden en el que se definen las rutas es muy importante. Si la ruta blog_show se coloca por encima de la ruta blog, la URL /blog/2 ejecutara la rutablog_show en lugar de blog ya que el parmetro {slug} de blog_show no tiene ningn requisito. Usando un orden adecuado para las rutas y los requisitos necesarios, puedes dominar completamente el sistema de enrutamiento. Como el parmetro requirements es una serie de expresiones regulares, puedes hacer que los requisitos sean tan complejos y flexibles como necesites. Imagina que la portada de tu aplicacin est disponible en dos idiomas en funcin de la URL accedida: YAML XML PHP homepage: path: /{_locale} defaults: { _controller: AcmeDemoBundle:Main:homepage, _locale: en } requirements: _locale: en|fr Cuando llegue una peticin de algn usuario, el valor indicado en la variable {_locale} se compara con la expresin regular (en|fr). Ruta Valor de la variable {_locale} / en /en en /fr fr /es Ninguno, ya que la ruta no se ejecuta 6.3.5. Agregando requisitos sobre el mtodo HTTP Adems de los requisitos de la URL, es posible restringir la ejecucin de las rutas a un determinado mtodo HTTP (es decir, GET, HEAD, POST, PUT, DELETE). Imagina que dispones de un formulario de contacto con dos controladores: uno para mostrar el formulario (en una peticin GET) y otro para procesar el formulario enviado (en una peticin POST*). La siguiente configuracin permite definir estas dos rutas: YAML XML PHP contact: path: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } methods: [GET]
contact_process: path: /contact defaults: { _controller: AcmeDemoBundle:Main:contactProcess } methods: [POST] NOTA La opcin methods solamente est definida desde la versin 2.2 de Symfony. En las versiones anteriores esta opcin se llamaba _method y se defina bajo la opcin requirements. A pesar de que estas dos rutas tienen el mismo patrn (/contact), la primera ruta slo se activa con las peticiones GET y la segunda slo con las peticiones POST. Esto significa que puedes mostrar y enviar el formulario a travs de la misma URL y usar dos controladores distintos para las dos acciones. NOTA Si no especificas la opcin methods, la ruta se ejecuta para todos los mtodos HTTP. 6.3.6. Agregando el host a las rutas A partir de la versin 2.2 de Symfony, tambin puedes hacer que una ruta slo se ejecute si coincide con el host configurado. Para obtener ms informacin, consulta el artculo How to match a route based on the Host. 6.3.7. Definiendo condiciones personalizadas mediante expresiones Hasta ahora, se ha explicado cmo restringir las rutas para que slo se ejecuten cuando se cumplen determinadas condiciones en sus variables, mtodos HTTP o nombres de host. Sin embargo, a partir de la versin 2.4 de Symfony, las rutas disponen de una flexibilidad casi ilimitada gracias a las condiciones definidas mediante la propiedad condition: YAML XML PHP contact: path: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.g et('User-Agent') matches '/firefox/i'" El valor de la propiedad condition es una expresin cuya sintaxis se define en la documentacin del componente ExpressionLanguage. En este ejemplo, la ruta no se ejecutar a menos que el mtodo HTTP sea GET o HEAD y el navegador sea Firefox (ya que la cabecera User-Agent debe contener la palabra firefox). La lgica de la expresin puede ser tan compleja como sea necesaria. Dentro de la expresin puedes utilizar las dos siguientes variables: context: es una instancia de la clase Symfony\Component\Routing\RequestContext, y contiene toda la informacin bsica de la ruta que est siendo comprobada. request: el objeto de tipo Symfony\Component\HttpFoundation\Request que representa a la peticin del usuario. ADVERTENCIA Las condiciones no se tienen en cuenta al generar las URL a partir de las rutas. Las expresiones se compilan a cdigo PHPLas expresiones siempre se compilan a cdigo PHP antes de ejecutarlas. En el caso del ejemplo mostrado anteriormente, este sera el cdigo PHP generado en el directorio cache de la aplicacin: if (rtrim($pathinfo, '/contact') === '' && ( in_array($context->getMethod(), array(0 => "GET", 1 => "HEAD")) && preg_match("/firefox/i", $request->headers->get("User-Agent")) )) { // ... } Por todo ello, el uso de la propiedad condition no supone ninguna penalizacin en el rendimiento, ms all de lo que cueste ejecutar el cdigo PHP compilado de la expresin. 6.3.8. Ejemplo de enrutado avanzado Con todo lo explicado hasta ahora, ya puedes definir rutas realmente avanzadas para tu aplicacin Symfony. El siguiente ejemplo muestra lo flexible que puede llegar a ser el sistema de enrutamiento: YAML XML PHP article_show: path: /articles/{_locale}/{year}/{title}.{_format} defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } requirements: _locale: en|fr _format: html|rss year: \d+ Esta ruta slo se ejecutar cuando el valor de la variable {_locale} sea o en o fr y cuando la variable{year} sea un nmero. Adems, esta ruta tambin muestra cmo utilizar un punto (.) para separar dos variables entre s, en vez de la habitual barra inclinada (/). En resumen, cualquiera de las siguientes URL haran que esta ruta se ejecutara: /articles/en/2010/my-post /articles/fr/2010/my-post.rss /articles/en/2013/my-latest-post.html El parmetro especial de enrutado _formatEl ejemplo anterior utiliza un parmetro especial de enrutamiento llamado _format. Cuando una ruta incluye esta variable, su valor se considera como el formato de la peticin y as se aade en el objeto Request correspondiente. Este valor es importante porque se utiliza, entre otros, para indicar el Content-Type de la respuesta enviada al usuario (por ejemplo, un formato de peticinjson se traduce en un Content-Type igual a application/json). Este valor tambin se puede utilizar en el controlador para renderizar una plantilla diferente en funcin de cada formato. De hecho, la variable _format es una de las formas ms prcticas de mostrar los mismos contenidos con diferentes formatos. NOTA En ocasiones es conveniente hacer que algunas partes de las rutas se puedan configurar globalmente en la aplicacin. A partir de la versin 2.1 de Symfony es posible hacerlo gracias a los parmetros del contenedor de servicios. Para ms informacin, lee el artculo How to use Service Container Parameters in your Routes. 6.3.9. Variables de enrutamiento especiales Como hemos visto, cada variable de enrutamiento y cada valor aadido al array defaults est disponible como argumento del controlador. Symfony tambin define tres variables especiales de enrutamiento: _controller: determina el controlador asociado a la ruta que se ejecuta. _format: establece el formato de la peticin del usuario. _locale: establece el idioma en la peticin del usuario. 6.4. El nombre lgico de los controladores Todas las rutas definen una variable llamada _controller que indica el controlador que se debe ejecutar. El valor de esta variable es una cadena de texto que indica el controlador utilizando una notacin especial llamada nombre lgico del controlador. Este nombre se divide en tres partes, cada una separada por dos puntos: bundle:controlador:accin As que por ejemplo, el valor AcmeBlogBundle:Blog:show hara referencia al bundle + AcmeBlogBundle, la claseBlogControllery el mtodoshowAction`. El cdigo del controlador podra ser algo como lo siguiente: // src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller { public function showAction($slug) { // ... } } Ten en cuenta que Symfony sufija automticamente la cadena Controller al nombre de la clase (Blogse transforma en BlogController) y Action al nombre del mtodo (show se transforma en showAction). Tambin es posible hacer referencia al controlador utilizando el namespace completo de su clase:Acme\BlogBundle\Controller\BlogController::showAction. Pero si lo piensas un poco, es mucho mejor utilizar el nombre lgico, poruqe adems permite una mayor flexibilidad. NOTA Adems de utilizar el nombre lgico o el namespace completo de la clase, Symfony define una tercera forma de referirse al controlador. Este mtodo utiliza un solo separador de dos puntos (por ejemplo, service_name:indexAction) y puedes emplearlo cuando defines tus controladores como servicios, tal y como se ver ms adelante. 6.5. Variables de la ruta y argumentos del controlador Las variables o parmetros de la ruta (por ejemplo, {slug}) son muy importantes porque estn disponibles como argumentos del mtodo del controlador: public function showAction($slug) { // ... } En realidad, todas las variables del array defaults se combinan con las variables de la ruta para formar un solo array. Cada clave de ese array conjunto est disponible como un argumento del controlador. En otras palabras, por cada argumento de tu mtodo controlador, Symfony busca una variable de ruta con ese nombre y asigna su valor a ese argumento. En el ejemplo avanzado anterior, cualquier combinacin de las siguientes variables (y en cualquier orden) se podra utilizar como argumentos para el mtodo showAction(): $_locale $year $title $_format $_controller Como se combinan las variables con los valores definidos en defaults, el controlador tiene a su disposicin incluso variables como $_controller. TRUCO Una variable especial adicional que siempre est disponible es $_route, que indica el nombre de la ruta que se est ejecutando. 6.6. Importando recursos de enrutamiento externos Todas las rutas de la aplicacin se cargan a travs de un nico archivo de configuracin normalmente app/config/routing.yml. Sin embargo, resulta habitual tener que cargar rutas definidas en otros archivos, almacenados por ejemplo dentro de algn bundle. Para cargarlas, debes importar esos ficheros externos utilizando las siguientes instrucciones: YAML XML PHP # app/config/routing.yml acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" NOTA Cuando importas recursos desde archivos YAML, el valor que asignes como clave de la opcin resource es indiferente, ya que no se utiliza para nada (en el ejemplo anterior, se utiliza el valor acme_hello). En cualquier caso, asegrate de que utilizas un valor nico para cada importacin. La clave resource indica el recurso desde el que se deben cargar las rutas. En este ejemplo, este recurso es la ruta completa de un archivo del sistema, donde la cadena @AcmeHelloBundle es un atajo muy til que Symfony interpreta como la ruta hasta el directorio donde se encuentra ese bundle. El contenido del archivo importado podra ser por ejemplo el siguiente: YAML XML PHP # src/Acme/HelloBundle/Resources/config/routing.yml acme_hello: path: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index } Las rutas de este archivo se analizan y cargan en la misma forma que las del archivo de enrutamiento principal. 6.6.1. Prefijando las rutas importadas Resulta habitual tener que aadir un prefijo a todas las rutas importadas desde un archivo externo. Si quieres por ejemplo que el patrn de la ruta acme_hello sea /admin/hello/{name} en vez de/hello/{name}, aade la opcin prefix al importar las rutas: YAML XML PHP # app/config/routing.yml acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: /admin El valor indicado en la opcin prefix (en este caso /admin) se aade por delante de todos los patrones de las rutas importadas desde el archivo externo. 6.6.2. Agregando un host a las rutas importadas A partir de la versin 2.2 de Symfony, tambin puedes aadir un host a las rutas importadas. Para obtener ms informacin, consulta el artculo Adding a Host Regex to Imported Routes. 6.7. Visualizando y depurando rutas A medida que aades y configuras rutas puede venir bien obtener informacin detallada sobre todas o alguna ruta especfica. La mejor manera de ver todas las rutas definidas para la aplicacin consiste en ejecutar el comando router:debug desde el directorio raz de tu proyecto: php app/console router:debug Como resultado, este comando imprime la lista de todas las rutas existentes en tu aplicacin: homepage ANY / contact GET /contact contact_process POST /contact article_show ANY /articles/{_locale}/{year}/{title}.{_format} blog ANY /blog/{page} blog_show ANY /blog/{slug} Para obtener toda la informacin sobre una nica ruta, aade el nombre de esa ruta como argumento del comando anterior: php app/console router:debug article_show De la misma forma, para comprobar si una determinada URL cumple con las condiciones de una ruta, puedes utilizar el comando router_match: $ php app/console router:match /blog/my-latest-post El resultado del comando muestra la ruta que se ejecutar para la URL indicada: Route "blog_show" matches 6.8. Generando URL El sistema de enrutamiento no solo se utiliza para asociar rutas a controladores sino que tambin se emplea para generar URL. De hecho, el enrutamiento siempre es un sistema bidireccional: convierte una URL + variables en un controlador y despus, convierte una ruta y varias variables en una URL. Este sistema bidireccional se basa en los mtodos match() y generate(). El siguiente cdigo emplea la ruta blog_show definida anteriormente: $params = $router->match('/blog/my-blog-post'); // array( // 'slug' => 'my-blog-post', // '_controller' => 'AcmeBlogBundle:Blog:show' // )
$uri = $router->generate('blog_show', array('slug' => 'my-blog-post')); // /blog/my-blog-post Para generar una URL, especifica el nombre de la ruta (por ejemplo, blog_show) y el valor de todas las variables de la ruta (por ejemplo, slug = my-blog-post). Con esta informacin, puedes generar fcilmente cualquier URL: class MainController extends Controller { public function showAction($slug) { // ...
$url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') ); } } Ms adelante se explicar tambin cmo generar URL desde las propias plantillas de la aplicacin. TRUCO Si tu aplicacin realiza peticiones AJAX, seguramente necesitars generar URL de Symfony desde el propio cdigo JavaScript. Para ello debes utilizar un bundle desarrollado por terceros y llamado FOSJsRoutingBundle: var url = Routing.generate('blog_show', { "slug": 'my-blog-post'}); 6.8.1. Generando URL con parmetros El array que se pasa al mtodo generate contiene los valores de las variables de la ruta, pero tambin puede contener variables adicionales que se aaden a la URL generada en forma de query string: $router->generate('blog', array('page' => 2, 'category' => 'Symfony')); // /blog/2?category=Symfony 6.8.2. Generando URL desde una plantilla La mayora de URL se generan en las propias plantillas para poder enlazar entre s las pginas de la aplicacin. El formato es muy parecido al explicado anteriormente, pero en el caso de las plantillas Twig, se utiliza una funcin especial llamada path(): TWIG PHP <a href="{{ path('blog_show', { 'slug': 'my-blog-post' }) }}"> Read this blog post. </a> 6.8.3. Generando URL absolutas El sistema de enrutamiento genera por defecto URL relativas (por ejemplo /blog). Para generar URL absolutas, pasa el valor true como tercer argumento del mtodo generate(): $router->generate('blog_show', array('slug' => 'my-blog-post'), true); // http://www.example.com/blog/my-blog-post En una plantilla, las URL absolutas se generan de la siguiente manera: TWIG PHP <a href="{{ url('blog_show', { 'slug': 'my-blog-post' }) }}"> Read this blog post. </a> NOTA El host que se utiliza en las URL absolutas es el mismo host de la peticin actual y su valor se detecta automticamente en funcin de la informacin proporcionada por PHP. Si ests generando URL absolutas en un script de consola, este mecanismo no funciona. Consultaeste artculo para saber cmo generar las URL absolutas en un comando de consola. 6.9. Resumen El enrutamiento es un sistema que permite asociar las URL de las peticiones de los usuarios a los controladores de tu aplicacin. Gracias a este sistema puedes generar URL limpias y adems, mantiene desacoplado el funcionamiento de tu aplicacin respecto de esas URL. Por ltimo, el enrutamiento es un sistema bidireccional, por lo que tambin se utiliza para generar URL a partir de la configuracin de las rutas.