Trabajando con el Controlador en Symfony2
En el capítulo anterior aprendimos a crear las rutas hacia nuestros controladores, como algunos ya saben el “Controlador” (o Controller) es la piedra angular de la arquitectura MVC, es lo que nos permite enlazar el modelo de la vista y básicamente donde se construye la aplicación.
Pero, ¿Que hace exactamente un controlador?
Bajo la filosofía de Symfony2 el concepto es simple y preciso: “El controlador recibe la petición (Request) y se encarga de crear y devolver una respuesta (Response)”; a simple vista pensarás “¡el controlador hace más que eso!” sí, pero a Symfony2 sólo le importa eso, lo que implementes dentro de él depende de tu lógica de aplicación y negocio*, al final un controlador puede:
- Renderizar una vista.
- Devolver un contenido tipo XML, JSON o simplemente HTML.
- Redirigir la petición (HTTP 3xx).
- Enviar mails, consultar el modelo, manejar sesiones u otro servicio.
Te darás cuenta de que todas las funciones anteriores terminan siendo o devolviendo una “Respuesta” al cliente, que es la base fundamental de una aplicación basada en el ciclo Web (Petición -> acción -> Respuesta). En este capítulo nos concentraremos en las funciones básicas que puedes hacer en el controlador, su estructura básica y las funciones más comunes.
[tipexperto titulo =”Nota”] *Las buenas prácticas indican que la lógica de negocios debe residir en el modelo (modelos gordos, controladores flacos).[/tipexperto]
Definiendo el controlador
Los controladores en Symfony2 se declaran como archivos con el sufijo Controller “MicontroladorController.php” en el directorio Mibundle/Controller dentro de nuestro Bundle, quedando así nuestro ejemplo: “src/MDW/DemoBundle/Controller/DemoController.php”, veamos el código:
<?php // src/MDW/DemoBundle/Controller/DefaultController.php namespace MDW\DemoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller { public function indexAction() { //$response = new \Symfony\Component\HttpFoundation\Response('Hola mundo!!!'); return new Response('Hola mundo!!!'); } }
Vemos que el controlador es una clase que extiende de la clase base Controller (Symfony\Bundle\FrameworkBundle\Controller\Controller), pero en realidad esta clase es un paquete de controladores, ¡sí, es confuso!: nuestros verdaderos controladores son en realidad las funciones (acciones) que contiene esta clase, cuyo sufijo siempre sea Action.
En la mayoría de los FW el Controlador contiene varias Acciones y si nos damos cuenta es en las Acciones en donde aplicamos la verdadera lógica del controlador, consideralo como un cliché; además Symfony2 no requiere que la clase controlador extienda de Controller, la clase base Controller simplemente nos provee de atajos útiles hacia las herramientas del núcleo del framework, como el DI (inyección de dependencias), por eso se recomienda su uso.
Como primera norma tenemos que asignar el “namespace” a nuestro controlador (línea #3), para asegurar que el mecanismo de autoload pueda encontrar nuestro controlador y con ello el Routing (entre otros) pueda hallarlo eficientemente, luego vemos una serie de declaraciones use que nos sirven para importar los diferentes espacios de nombres y clases que necesitamos para trabajar.
Namespaces: Es sabido que para algunos este cambio es brusco, pero tiene su objetivo claro el cual otros FW siguen y dentro de poco otros seguirán, entre ellos evitar conflictos con los nombres de clases, pero bueno ¿te frustra escribir tanto código?: tranquilo, que si usas un IDE inteligente como Netbeans 7 con solo hacer nombre a la clase puede generarte automáticamente la cadena de referencia completa (como puedes notar en la línea comentada #12), claro está que utilizar use es más elegante y forma parte de las buenas prácticas.
Al final vemos que simplemente creamos nuestra respuesta (objeto Response) y lo devolvemos, eso es lo básico de nuestro controlador, más adelante expondremos los detalles.
Renderizando (generando) Vistas
Prácticamente es la tarea más común en cada controlador en la cual hacemos la llamada al servicio de Templating, Symfony2 por defecto utiliza Twig y los detalles se expondrán en el próximo capitulo, por ahora nos concentraremos en la forma de renderizar nuestras vistas, para eso añadimos la siguiente acción en nuestro BlogController que nos corresponderá a la ruta “blog_index” definida en el capítulo anterior:
// src/MDW/DemoBundle/Controller/BlogController.php // ... public function showAction($slug) { $articulo = $slug; // (suponiendo que obtenemos el artículo del modelo con slug) return $this->render('MDWDemoBundle:Blog:show.html.twig', array( 'articulo' => $articulo )); } // ...
La función $this->render() acepta 2 parámetros: el primero es un patrón para encontrar la plantilla [Bundle]:[Controller]:template.[_format].template_engine y el segundo el array para pasar variables a la plantilla, devolviendo un objeto Response().
Como apreciarás en el patrón, Symfony busca la correspondiente plantilla en el directorio MyBundle/Resoruces/views, según su propia estructura interna detallada en el próximo capítulo de la Vista. Como indicamos en el capítulo anterior sobre Routing, los parámetros de nuestro Controlador coinciden con los comodines definidos en la ruta con el cual obtendremos eficientemente estos datos.
En realidad la función $this->render() es un atajo de nuestra clase base Controller que renderiza la plantilla y crea el Response, al final tenemos estas diferentes formas para hacer el mismo proceso:
Otras formas de renderizar plantillas:
// 2da forma- atajo que obtiene directamente el contenido renderizado: $content = $this->renderView('MDWDemoBundle:Blog:show.html.twig', array('articulo' => $articulo)); return new Response($content); // 3ra forma- obteniendo el servicio templating por DI (inyección de dependencias): $templating = $this->get('templating'); $content = $templating->render('MDWDemoBundle:Blog:show.html.twig', array('articulo' => $articulo)); return new Response($content);
Como puedes apreciar, los atajos de la clase base Controller son de mucha ayuda, y si en dado caso necesitas mayor flexibilidad puedes optar por la 2da y 3ra forma. 😉
Obteniendo datos de la Petición (Request)
Ya sabes que los parámetros del Controlador coinciden con los comodines definidos en las Rutas, pero existe otra forma de acceder a las variables POST o GET pasadas a nuestra petición, y como es obvio lo proporciona el objeto Request(), existen varias formas de obtener el objeto Request, incluso como en Symfony1 en Symfony2 lo puedes pasar como parámetro del controlador, como el sistema de Routing empata éstos con los comodines el orden no importa:
// src/MDW/DemoBundle/Controller/BlogController.php // ... use Symfony\Component\HttpFoundation\Request; //es necesario para importar la clase //... public function showAction(Request $peticion, $slug) { $articulo = $peticion->get('slug'); // otra forma para obtener comodines, GET o POST $metodo = $peticion->getMethod(); //obtenemos si la petición fue por GET o POST return $this->render('MDWDemoBundle:Blog:show.html.twig', array( 'articulo' => $articulo )); }
Vemos que con el objeto Request obtenemos por completo los datos de nuestra petición, además la clase base Controller dispone de un atajo para obtener el Request sin necesidad de pasarlo como parámetro y de esa forma nos ahorraremos la importación por use:
$peticion = $this->getRequest(); //lo que es lo mismo que con DI: $peticion = $this->get('request');
Redirecciones
En diversas oportunidades nos vemos obligados en hacer una redirección, ya sea por haber procesado un POST, o dependiendo de la lógica que apliquemos en el Controlador, si en dado caso tengamos que redirigir a HTTP 404 por un registro en el modelo no encontrado, etc.
public function indexAction() { return $this->redirect($this->generateUrl('homepage')); //return $this->redirect($this->generateUrl('homepage'), 301); }
Como notarás la función $this->redirect() acepta como primer parámetro una string Url (la función $this->generateUrl() nos permite generar la url desde una ruta definida en Routing) y por defecto realiza una redirección HTTP 302 (temporal), en el segundo parámetro puedes modificarla para hacer una 301 (permanente).
En el caso de redirigir a HTTP 404, podremos causar una Excepción de esta forma que Symfony2 intercepta internamente:
public function indexAction() { $producto = false;// suponiendo que nuestro modelo devuelva false al no encontrarlo if (!$producto) { throw $this->createNotFoundException('No existe el producto'); } return $this->render(...); }
En ocasiones necesitarás pasar la petición a otro controlador internamente sin necesidad de redirecciones HTTP, en este caso el Forwarding es tu alternativa:
public function indexAction($name) { $respuesta = $this->forward('MDWDemoBundle:Blog:index', array( 'slug' => $name )); // puedes modificar directamente la respuesta devuelta por el controlador anterior. return $respuesta; }
Como podrás notar, en el primer parámetro de la función $this->forward() acepta el mismo formato que utilizamos en el Routing para hallar el Controlador, y como segundo parámetro un array con los parámetros de dicho Controlador, cabe mencionar que el mismo ha de ser un controlador normal y no es necesario que tenga una ruta en Routing definida.
Resumen Final
Como apreciamos en éste capítulo, los Controladores en Symfony2 requieren el sufijo Controller para ser hallados por el FW, además de resaltar que la verdadera lógica del controlador reside en sus Acciones las cuales son las funciones internas de los mismos declaradas con el sufijo Action, además de ello vimos las funcionalidades básicas del controlador, como lo son acceder a las variables de la petición “Request”, Redireccionar, pasar a otros controladores sin redirecciones (Forwarding) y el renderizado de las vistas.
En el próximo capitulo “La Vista y Twig” veremos la flexibilidad que nos permite este maravilloso sistema de plantillas, además de enumerar las operaciones básicas de la Vista en Symfony2 y como aprovechar el sistema de Herencia de plantillas para lograr una estructura de layouts dinámicos y reutilizables.
Muy buena guía!. Yo en algún tiempo empezaré con Symfony 2 para ver que tan poderoso me siento haha. Gracias por la guía.
Por cierto, en mi opinión Twig es el mejor sistema de plantillas, aún comparándolo con Smarty, el favorito de muchos.
Te han dicho que eres muy bueno explicando? haces que conceptos complicados sean fáciles de leer y entender. Muchas Gracias por la guía.
Cielos, gracias!!!, si mis compañeros de clase ni me soportan cuando hablo X-D, bueno, me esmeré mucho escribiendo de la forma más clara y sencilla posible, y no faltaron las horas de revisión, espero que disfrutes mucho de la guía, no sólo de mis capítulos, sino los de Juan también, saludos 😉
[…] Guía de Symfony2 – Capítulo 6 – Trabajando con el Controlador VN:F [1.9.15_1155]please wait…Rating: 0.0/5 (0 votes cast)En los capítulos anteriores de la guía de Symfony2 estuvimos hablando sobre sobre la configuración del routing y hoy se ha publicado ya el siguiente capítulo con un nuevo tema muy importante, el controlador. […]
[…] Alvarez para Maestros del Web. Agrega tu comentario | Enlace permanente al […]
Hola Maycol, estoy utilizando symfony2 y hay un comportamiento extraño que no se cómo controlar.
Estoy utilizando el bundle IoTcpdfBundle. Genero un documento .pdf (sin recibir datos de ningún formulario para imprimirlos en el .pdf) y el comportamiento es el esperado, al darle click derecho y luego seleccionar “Guardar como” el explorador ofrece la opción de guardar en .pdf.
El problema está cuando quiero generar el .pdf pero utilizando datos que viajan por el post. El explorador me muestra el archivo .pdf, pero no me da la opción de guardar como .pdf, sino como .htm. Y al guardar el archivo .htm lo que se guarda es la vista anterior (donde tenía el formulario del cual recibo los valores).
Este es el código por si podría darte alguna pista para ayudarme.
if ($request->getMethod() == ‘POST’) {
$year = $this->get(‘request’)->request->get(‘year’);
$date= $this->get(‘request’)->request->get(‘date’);
$html = $this->renderView(‘MyBundle:Documentales:solicitud.pdf.twig’, array(‘year’ => $year, ‘date’ =>$date));
return $this->get(‘io_tcpdf’)->quick_pdf($html);
}
Este código muestra en el explorador el .pdf, pero no me permite guardarlo en ese formato.
muy buen trabajo, te agradeceria tambien si puedes tocar cosas avanzadas como tips etc, cosas que vemos en el libro de Javier o mas aun. Gracias.
Aveces por los plugins lectores de PDF que tengamos en nuestros navegadores los mismos tratan de “visualizar” el PDF en vez de descargarlos, por lo cual es necesario aplicar Headers HTTP especiales para forzar a los navegadores a un “Force Download”, como me imagino que la función quick_pdf($html); devuelve un objeto Response, sólo debes añadir los headers necesarios:
$response = $this->get(‘io_tcpdf’)->quick_pdf($html); //SUPONIENDO que devuelva un Response()
$response->setStatusCode(200);
$response->headers->set(‘Content-Type’, ‘application/pdf’);
$response->headers->set(‘Content-Disposition’,
sprintf(‘attachment;filename=”%s.pdf”‘, “NOMBRE del PDF”));
$response->send();
return $response;
La Guía está diseñada para comenzar con lo básico hasta ir a lo específico y complejo, en los capítulos finales se notará el cambio, también se disponen de tips en base a nuestras experiencias (Juan y mías) dependiendo del capítulo, no nos basamos en trabajos de otros, la referencia fundamental fue la Documentación Oficial de Symfony2 y Doctrine2 en inglés, gracias por la recomendación.
Muy buena guia!!!! Me interesaría saber como podría personalizar las páginas de error, y el control de excepciones???
http://symfony.com/doc/current/cookbook/controller/error_pages.html en inglés pero fácil de comprender
Fabuloso, solamente un apunte, donde se habla de “funciones” en realidad son métodos (creo que sería más correcto llamarlos de este modo) ya que pertenecen a una clase (en este caso a la clase ‘DefaultController’). Por lo demás , genial vuestra guía.
bueno, en PHP el término “procedure” no existe (al menos que yo sepa), todos son funciones, así no retornen nada, pero en el caso de Symfony2 si son funciones porque su objetivo es retornar un objeto Response(), por lo cual de lo contrarío no funcionaría como se debiera 😉
como sugerencia a Maycol, me parece que estos temas serían aun mejor explicados si das continuidad a los ejemplos de los articulos iniciales, o si se hacen ejemplos practicos para afianzar mas los conocimientos (es diferente aprender un conocimiento enunciado a adicionalmente confrontarlo con ejemplos practicos). Es solo una sugerencia, por que entiendo que eres programador y no experto en pedagogía ni en didáctica, aun asi es un muy buen material.
Bueno, tengo una pregunta, que pasa si en symfony no queremos usar twig, me parece que esta muy amarrado a este componente y dudo si se pueda comunicar con un framework javascript para la parte visual sin tener que utilizar twig. agradezco quien me de una respuesta y si encuentran un ejemplo mucho mejor asi si sea la comunicación de un html con symfony 2. muchas gracias