Una solución mucho mejor es usar un controlador frontal: un único archivo PHP a través del cual se procesen todas las peticiones del usuario. Con un controlador frontal, la URI de la aplicación cambia un poco, pero se vuelve mucho más flexible:
Sin controlador frontal: /index.php => (ejecuta index.php) la página que lista los artículos. /show.php => (ejecuta show.php) la página que muestra un artículo específico. Con index.php como controlador frontal /index.php => (ejecuta index.php) la página que lista los artículos. /index.php/show => (ejecuta index.php) la página que muestra un artículo específico.
Truco El nombre del archivo index.php
se puede eliminar en las URLs si utilizas las reglas de reescritura del servidor web Apache (o las equivalentes de los demás servidores web). En ese caso, la URI resultante de la página show
del blog sería simplemente /show
.
Cuando se usa un controlador frontal, un solo archivo PHP (index.php
en este caso) procesa todas las peticiones. Para la página show
del blog, /index.php/show
realmente ejecuta el archivo index.php
, que ahora es el responsable de dirigir internamente las peticiones basándose en la URI completa. Como puedes ver, un controlador frontal es una herramienta muy poderosa.
2.3.1. Creando el controlador frontal
Estás a punto de dar un gran paso en la aplicación. Con un archivo manejando todas las peticiones, puedes centralizar cosas como el manejo de la seguridad, la carga de la configuración y el enrutamiento. En nuestra aplicación, index.php
ahora debe ser lo suficientemente inteligente como para mostrar la lista de entradas del blog o mostrar la página de una entrada particular basándose en la URI solicitada:
<?php
// index.php
// carga e inicia algunas librerías globales
require_once 'model.php';
require_once 'controllers.php';
// encamina la petición internamente
$uri = $_SERVER['REQUEST_URI'];
if ($uri == '/index.php') {
list_action();
} elseif ($uri == '/index.php/show' && isset($_GET['id'])) {
show_action($_GET['id']);
} else {
header('Status: 404 Not Found');
echo '<html><body><h1>Page Not Found</h1></body></html>';
}
Para organizar mejor el código, los dos controladores anteriores (archivos index.php
y show.php
) se han convertido en funciones PHP del nuevo archivo controllers.php
:
function list_action()
{
$posts = get_all_posts();
require 'templates/list.php';
}
function show_action($id)
{
$post = get_post_by_id($id);
require 'templates/show.php';
}
Como controlador frontal, index.php
ha asumido un papel completamente nuevo, que incluye la carga de los archivos principales de la aplicación (model.php
y controllers.php
) y la decisión de ejecutar uno de los dos controladores (las funciones list_action()
y show_action()
). En realidad, el controlador frontal está empezando a parecerse y actuar como el mecanismo que define Symfony2 para la manipulación y enrutado de peticiones.
Truco Otra ventaja del controlador frontal es la flexibilidad de las URL. Ten en cuenta que la URL de la página show
del blog se puede cambiar de /show
a /read
cambiando el código en un único lugar. Antes, era necesario cambiar todo un archivo para cambiar el nombre. En Symfony2, las URL son incluso más flexibles.
Por ahora, la aplicación ha evolucionado de un único archivo PHP, a una estructura organizada que permite la reutilización de código. Debes estar contento, pero aún lejos de estar satisfecho. Por ejemplo, el sistema de enrutamiento es muy mejorable, ya que no reconoce por ejemplo que la página list
(/index.php
) también debe ser accesible a través de /
(si has agregado las reglas de reescritura de Apache). Además, en lugar de programar el blog, has perdido tu tiempo en preparar la "arquitectura" del código (por ejemplo, el enrutamiento, los controladores, las plantillas, etc.) Y pronto tendrás que perder más tiempo en la gestión de los formularios, la validación de la información, crear un archivo de log, la gestión de la seguridad, etc.
¿Por qué tienes que dedicarte a solucionar estos problemas que se repiten una y otra vez en todos los proyectos?
2.3.2. Añadiendo un toque de Symfony2
¡Symfony2 al rescate! Antes de utilizar Symfony2, debes asegurarte de que PHP sabe cómo encontrar las clases Symfony2. Esto se consigue a través de un cargador automático de clases (class loader) que proporciona Symfony. Un cargador automático es una herramienta que permite empezar a utilizar clases PHP sin incluir explícitamente el archivo que contiene la clase.
Primero, descarga Symfony y colócalo en el directorio vendor/symfony/
.
A continuación, crea un archivo app/bootstrap.php
, que se usa para requerir los dos archivos en la aplicación y para configurar el cargador automático:
<?php
// bootstrap.php
require_once 'model.php';
require_once 'controllers.php';
require_once 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
$loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
$loader->registerNamespaces(array(
'Symfony' => __DIR__.'/../vendor/symfony/src',
));
$loader->register();
El código anterior le dice al cargador automático dónde están las clases de Symfony. Con esto, puedes comenzar a utilizar las clases de Symfony sin necesidad de añadir la declaración require
en los archivos que las utilizan.
La esencia de la filosofía Symfony es que el trabajo principal de una aplicación es interpretar cada petición y devolver una respuesta. Con este fin, Symfony2 proporciona las clases Request
y Response
. Estas clases son representaciones orientadas a objetos de la petición HTTP que se está procesando y la respuesta HTTP que se devolverá. Úsalas para mejorar el blog:
<?php
// index.php
require_once 'app/bootstrap.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$uri = $request->getPathInfo();
if ($uri == '/') {
$response = list_action();
} elseif ($uri == '/show' && $request->query->has('id')) {
$response = show_action($request->query->get('id'));
} else {
$html = '<html><body><h1>Page Not Found</h1></body></html>';
$response = new Response($html, 404);
}
// envía las cabeceras y la respuesta
$response->send();
Los controladores se encargan de devolver un objeto Response
. Para simplificar la generación del código HTML de la respuesta, crea una nueva función llamada render_template()
, la cual, por cierto, actúa un poco como el motor de plantillas de Symfony2:
// controllers.php
use Symfony\Component\HttpFoundation\Response;
function list_action()
{
$posts = get_all_posts();
$html = render_template('templates/list.php', array('posts' => $posts));
return new Response($html);
}
function show_action($id)
{
$post = get_post_by_id($id);
$html = render_template('templates/show.php', array('post' => $post));
return new Response($html);
}
// función ayudante para reproducir plantillas
function render_template($path, array $args)
{
extract($args);
ob_start();
require $path;
$html = ob_get_clean();
return $html;
}
Aunque solo ha añadido una pequeñísima parte de las ideas de Symfony2, la aplicación ahora es más flexible y fiable. El objeto Request
proporciona una manera segura para acceder a la información de la petición HTTP. El método getPathInfo()
por ejemplo devuelve una URI limpia (siempre devolviendo /show
y nunca /index.php/show
).
Por lo tanto, incluso si el usuario va a /index.php/show
, la aplicación es lo suficientemente inteligente para encaminar la petición hacia show_action()
.
El objeto Response
proporciona flexibilidad al construir la respuesta HTTP, permitiendo que las cabeceras HTTP y el contenido se definan a través de una interfaz orientada a objetos.
Y aunque las respuestas en esta aplicación son simples, esta flexibilidad será muy útil en cuanto tu aplicación crezca.
2.3.3. Aplicación de ejemplo en Symfony2
El blog ha mejorado bastante, pero todavía contiene una gran cantidad de código para ser una aplicación tan simple. Durante las mejoras de la aplicación, se ha creado un sistema de enrutamiento muy sencillo y otro método que utiliza ob_start()
y ob_get_clean()
para procesar plantillas. Si vas a continuar desarrollando este blog, lo mejor es que utilices los componentes Routing
y Templating
de Symfony para mejorar esa parte sin esfuerzo.
En lugar de resolver de nuevo los mismos problemas de siempre, deja que Symfony2 se encargue de ellos por ti. Aquí está la misma aplicación de ejemplo, pero construida ahora con Symfony2:
<?php
// src/Acme/BlogBundle/Controller/BlogController.php
namespace Acme\BlogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
public function listAction()
{
$posts = $this->get('doctrine')->getEntityManager()
->createQuery('SELECT p FROM AcmeBlogBundle:Post p')
->execute();
return $this->render('AcmeBlogBundle:Blog:list.html.php', array('posts' => $posts));
}
public function showAction($id)
{
$post = $this->get('doctrine')
->getEntityManager()
->getRepository('AcmeBlogBundle:Post')
->find($id);
if (!$post) {
// hace que se muestre la página de error 404
throw $this->createNotFoundException();
}
return $this->render('AcmeBlogBundle:Blog:show.html.php', array('post' => $post));
}
}
Los dos controladores siguen siendo más o menos pequeños. Cada uno utiliza la librería ORM de Doctrine para recuperar objetos de la base de datos y el componente Templating
para generar el código HTML con una plantilla y devolver un objeto Response
. La plantilla list
ahora es un poco más simple:
<!-- src/Acme/BlogBundle/Resources/views/Blog/lista.html.php -->
<?php $view->extend('::base.html.php') ?>
<?php $view['slots']->set('title', 'List of Posts') ?>
<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href="<?php echo $view['router']->generate('blog_show', array('id' => $post->getId())) ?>">
<?php echo $post->getTitle() ?>
</a>
</li>
<?php endforeach; ?>
</ul>
Y el diseño de la plantilla base también es muy parecido al de antes:
<!-- app/Resources/views/base.html.php -->
<html>
<head>
<title><?php echo $view['slots']->output('title', 'Default title') ?></title>
</head>
<body>
<?php echo $view['slots']->output('_content') ?>
</body>
</html>
Nota Te vamos a dejar como ejercicio la plantilla show
, porque debería ser trivial crearla basándote en la plantilla list
.
Cuando arranca el motor de Symfony2 (llamado kernel
), necesita un mapa para saber qué controladores ejecutar en base a la información solicitada por los usuarios. Este mapa se crea con la configuración de enrutamiento, que proporciona esta información en formato legible:
# app/config/routing.yml
blog_list:
pattern: /blog
defaults: { _controller: AcmeBlogBundle:Blog:list }
blog_show:
pattern: /blog/show/{id}
defaults: { _controller: AcmeBlogBundle:Blog:show }
Ahora que Symfony2 se encarga de todas las tareas repetitivas, el controlador frontal es muy simple. Y ya que hace tan poco, nunca tienes que volver a tocarlo una vez creado (y si utilizas una distribución Symfony2, ¡ni siquiera tendrás que crearlo!):
<?php
// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();
El único trabajo del controlador frontal es iniciar el motor de Symfony2 (Kernel
) y pasarle un objeto Request
para que lo manipule. Después, el núcleo de Symfony2 utiliza la información de enrutamiento para determinar qué controlador se ejecuta. Al igual que antes, el método controlador es el responsable de devolver el objeto Response
final. Y eso es todo lo que hace el controlador frontal.
2.3.4. Qué más ofrece Symfony2
En los siguientes capítulos, aprenderás más acerca de cómo funciona cada parte de Symfony y la organización recomendada de un proyecto. Ahora vamos a ver cómo nos ha facilitado nuestro trabajo de programadores el migrar el blog de PHP simple a Symfony2:
- Tu aplicación cuenta con código claro y bien organizado (aunque Symfony no te obliga a ello). Esto facilita la reutilización de código y permite a los nuevos desarrolladores ser productivos en el proyecto con mayor rapidez.
- El 100% del código que escribes es para tu aplicación. No necesitas desarrollar o mantener herramientas de bajo nivel como la carga automática de clases, el enrutamiento o los controladores.
- Symfony2 te proporciona acceso a herramientas de software libre tales como Doctrine, plantillas, seguridad, formularios, validación y traducción (por nombrar algunas).
- La aplicación ahora dispone de URLs totalmente flexibles gracias al componente
Routing
. - La arquitectura centrada en HTTP de Symfony2 te da acceso a herramientas muy potentes, como por ejemplo la caché HTTP (mediante la caché HTTP interna de Symfony2 o mediante herramientas más avanzadas como Varnish). Esto se explica más adelante en el capítulo sobre la caché.
Y lo mejor de todo, utilizando Symfony2, ¡ahora tienes acceso a un conjunto de herramientas de software libre de alta calidad desarrolladas por la comunidad Symfony2!
Puedes encontrar una buena colección de herramientas comunitarias de Symfony2 en KnpBundles.com.