Symfony es uno de los frameworks más utilizados, versátiles y potentes del mercado. Sus componentes como el gestor de plantillas Twig, su ORM/DBAL Doctrine o su firme acogida a los estandares le han hecho merecedor de una posición privilegiada entre las opciones de las grandes empresas. Está tras plataformas tan potentes como phpBB, drupal o blackfire. Tiempo atrás había hecho algún experimento para ver como funcionaba, pero a día de hoy estoy desactualizado. Voy a desarrollar una pequeña aplicación para tener una base desde la que iniciar un proyecto orientado a continuous-delivery, y así familiarizarme con su estructura y forma de trabajo. He hecho bases parecidas en CakePHP y trabajado con Zend, aunque en este último no llegué nunca a ver el CRUD generator. Los resultados son muy buenos, estaba especialmente contento con CakePHP, a ver que puedo sacar de Symfony.

A lo largo de varios posts generaré una aplicación esqueleto que implementará un control de usuarios (ACL), un sistema de menú, traducciones, los interfaces de estos sistemas y las plantillas necesarias para conformar un layout. Al comenzar un nuevo desarrollo nos quitaremos de encima la primera carga de trabajo que es montar la base. Con muy poco esfuerzo conseguiremos levantar una nueva instancia de aplicación para mostrar, y ganaremos tiempo para desarrollar las primeras pantallas de gestión de básicos. Hay que recordar que lo que no esta en producción no tiene valor, de manera que cuanto antes tengamos partes funcionales en producción antes empezaremos a generar.

Instalación

Teniendo composer instalado, la creación del proyecto será algo sencillo (Hice una pequeña introducción a Composer aqui). Lo más fácil para inicializar el proyecto será ubicarnos en el directorio donde vamos a crear el proyecto y escribir el comando. Este ya se encarga de crear la carpeta del proyecto y poblarla con una aplicación Symfony vacía.

1
composer create-project symfony/framework-standard-edition my_project_name

Al terminar la instalación de Symfony y sus dependencias, el build script nos pedirá los siguientes parámetros.

1
2
3
4
5
6
7
8
9
10
database_host (127.0.0.1):
database_port (null):
database_name (symfony):
database_user (root):
database_password (null):
mailer_transport (smtp):
mailer_host (127.0.0.1):
mailer_user (null):
mailer_password (null):
secret (ThisTokenIsNotSoSecretChangeIt):

Son las variables necesarias para la conexión a la base de datos y la configuración del cliente de correo de Symfony, más tarde se puede cambiar en el archivo app/config/parameters.yml.

Ya tenemos el esqueleto base para poder utilizar Symfony. Desde el shell/cmd navegamos al directorio raíz del proyecto, con el siguiente comando levantamos un servidor basado en el nativo de PHP. Es muy parecido al servidor HTTP de reactPHP y válido sólo para desarrollo.

1
2
cd my_project_name
php bin/console server:run

Podremos acceder a la aplicación con el navegador, a través de la url http://localhost:8000/.

Después de acceder a la pagina de presentación podemos acceder a través de la url http://localhost:8000/config.php a una pagina encargada de comprobar que la plataforma ha sido instalada correctamente. Durante su carga verifica varios parámetros y al final presentan algunos consejos para mejorar la experiencia Symfony. Como instalar la extensión intl, phpAccelerator o cambiar algún parámetro de la configuración.

Estructura en disco del proyecto Symfony

Al entrar al directorio del proyecto podemos comprobar que se han desplegado varias carpetas organizadas de la siguiente manera.

  • app
    Configuración, plantillas y traducciones de la aplicación.

  • bin
    En este directorio reside el ejecutable de consola, que nos vale para invocar las utilidades del cliente desde nuestro terminal.

  • src
    El código fuente de nuestra aplicación.

  • tests
    Almacena los sources de tests, ya sea PHPUnit, codeception u otra herramienta. Su estructura es una réplica de src

  • var
    El directorio temporal, aquí se almacenan los archivos de cache, log, sesión, etc..

  • vendor
    Contiene las librerias externas que utilicemos así como el core de Symfony, es el directorio que utiliza Composer para mantener las dependencias.

MVC

Para entender un poco la forma en que vamos a trabajar hace falta entender que Simfomy es un framework MVC, es decir, basa su trabajo en una estructura fija denominada Modelo-Vista-Controlador.

  • El modelo
    Mantiene la mayor parte de la lógica e interactúa con los datos (Base de Datos).

  • La vista
    Es el canal de salida, el puente para enviar los datos, que devuelve el modelo, al usuario que accede al sistema, por ejemplo través de un template HTML (interfaz), o un formato JSON esperado por una llamada tipo REST.

  • El controlador
    Atiende las peticiones o eventos, llama a la lógica de los modelos y retorna el resultado al cliente a través de la vista.

De esta forma se consigue una separación de conceptos clara a la hora de abordar un desarrollo.

En este caso solo vamos a tocar el código del controlador para poder interactuar con la aplicación, con esto podremos ver de manera básica las respuestas a nuestras peticiones.

Manos a la obra, El código

Vamos a seguir el ejemplo de la web de Symfony, como dijimos, el controlador es el encargado de recibir las peticiones o eventos así que empezaremos prescindiendo de modelos y vistas. Generaremos un controlador que ante una request, determinada por una ruta, retornará un pequeño código HTML al cliente. Así que creamos un fichero llamado BlogController.php dentro de la carpeta src/AppBundle/Controller, con el siguiente código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;

class BlogController extends Controller
{
/**
* @Route("/blog")
*/
public function listAction()
{
return new Response(
'<html><body>Reading Blog index page</body></html>'
);
}
}

Su cometido es devolver el índice de noticias al acceder con el navegador a la ruta http://localhost:8000/blog, si aun tenemos en marcha el servidor para el primer test pulsamos ctrl+c para detenerlo, y/o, volvemos a arrancar el proceso con el mismo comando.

1
php bin/console server:run

Controladores y acciones

El controlador puede ser cualquier callable PHP, una closure, una función o un método dentro de una clase, que lee información de un objeto Request y retorna un objeto Response. Se suele utilizar un método de una clase, el nombre de este objeto es bueno que incluya el sufijo Controller (BlogController), utilizado por convenio, muchas funciones del framework esperan encontrar y utilizar este sufijo.

Generalmente esta clase se encarga de congregar las distintas acciones relativas a una misma entidad dentro del programa, como usuarios, noticias, comentarios.. Las acciones son métodos públicos dentro del objeto controlador, definidos para manejar un evento o request concreta. Las acciones, por convenio, se definen con el sufijo Action (listAction). En realidad estas acciones son el controller real para la petición definida, pero es costumbre llamar controller al objeto y action a las funciones que congrega.

Symfony nos provee con un base Controller, Symfony\Bundle\FrameworkBundle\Controller\Controller que no es obligatorio extender con el nuestro, pero hacerlo nos garantiza el acceso a métodos helper y al container de servicios. Estos helpers nos ayudaran con determinadas rutinas como escribir los enlaces a distintas partes de nuestra aplicacion, redirigir al usuario hacia algun punto determinado, o renderizar un template twig para inyectarlo en una response que posteriormente devolveremos. Despues de extender el Controller de Symfony tendremos acceso a estos métodos.

  • generateUrl()
    Este helper genera una url válida para una ruta definida en el sistema.

    1
    $url = $this->generateUrl('blog_list', array('page' => 'slug-value'));
  • redirectToRoute()
    Retorna un onjeto response con los headers peparados para realizar una redireccion a una ruta definida en el sistema. Si la acción retorna este objeto se producirá la redirección en el cliente.

    1
    2
    3
    4
    5
    public function indexAction()
    {
    // redirect to the "listAction" route
    return $this->redirectToRoute('blog_list', array('page' => 2));
    }
  • render()
    Prepara y genera el contenido a partir de una plantilla (Template), retorna una reponse con este contenido ya inyectado.

    1
    2
    3
    return $this->render('blog/number.html.twig', array(
    'name' => $name
    ));

Las rutas

Las rutas en Symfony son patrones complejos que informan a la aplicación sobre que acción dentro de cada controlador resolverá una petición determinada. También puede definir los parámetros esperados por esa acción.

Se definen en los comentarios de cada método, en la primera ejecución, el core de Symfony recogerá estas anotaciones y las dispondrá en la cache para las siguientes ejecuciones. De esta manera el código es más claro y evitamos tener la información repartida por distintos ficheros en el sistema.

1
2
3
4
5
6
7
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"})
*/
public function listAction($page = 1)
{
// Code Here.
}

Esta es una ruta definida en los comentarios de una function, la declaración de la ruta se realiza mediante el tag @Route(ruta, nombre, requisito), sus parámetros estaba medio camino entre el HTML y la programación estructurada.

  • ruta, entre comillas definiremos la parte de la url a la que hay que llamar, sin incluir el dominio. Por ejemplo /users o /blog/comments. Se acepta una sintaxis que nos permite establecer una parte de la ruta como variable y que la queremos recibir como parámetro en la acción, esto se denomina placeholder. Así que si definimos un placeholder, un nombre entre llaves en la ruta, “/blog/{placeholder}”, podremos recibir en la acción una variable $placeholder con ese valor y presentar solo la información de la página requerido en la petición.

  • nombre, es el nombre interno que podremos utilizar para identificar las rutas que definimos. sigue el formato name=”nombreRuta”

  • requisito, es un array JSON de requisitos para los placeholders, es decir, una expresión regular que define si una parte de la ruta se puede considerar válida para inyectar al placeholder calificado. De esta manera permite discriminar o encauzar rutas en función de su valor. Pongamos el caso que definimos dos rutas muy parecidas.

Para hacer algún test con los requisitos y parámetros podemos crear un fuente dentro de src/AppBundle/Controller denominado BlogController.php en el que escribiremos el siguiente código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class BlogController extends Controller
{
/**
* @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"})
*/
public function listAction($page=1)
{
return new Response(
'<html><body>Showing page number: '.$page.'</body></html>'
);
}

/**
* @Route("/blog/{title}", name="blog_read", requirements={"title": "\S+"})
*/
public function readAction($title)
{
return new Response(
'<html><body>Showing post:'.$title.'</body></html>'
);
}
}

El requisito de la primera ruta es que el placeholder debe ser numérico, de manera que si pasamos un titulo alfanumérico en el placeholder, descartará la ejecución de la listAction por que este valor no es un número, tal y como hemos indicado.

Para poder establecer un valor por defecto para un placeholder, basta con asignarle el valor por defecto al parámetro de la acción llamada como el placeholder, public function listAction($page = 1)

Las rutas pueden ser descritas como anotaciones en el código, pero también se pueden generar en un archivo específico (app/config/routing.yml). Aquí podremos describir más rutas para la aplicación, e incluso se puede definir para que incluya rutas de otras fuentes. En este caso seria necesario borrar previamente las que tengamos definidas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app/config/routing.yml
#
# Estas lineas definen el comportamiento
# estandar de leer la rutas de las anotaciones del código
app:
resource: "@AppBundle/Controller/"
type: annotation
# Al definir las rutas en los controladores estas rutas son innecesarias, no utilizar
blog_list:
path: /blog/{page}
defaults: { _controller: AppBundle:Blog:list, page: 1 }
requirements:
page: '\d+'
blog_read:
path: /blog/{title}
defaults: { _controller: AppBundle:Blog:read }
requirements:
title: '\S+'

Fin de la primera parte

Con esto tenemos ya montado el core del framework, así como nuestros primeros controladores. Symfony es inmenso, para poder hablar un poco con detalle de algunas de sus partes voy a dividir el contenido en una serie de posts comentando lo que creo más útil para empezar a usarlo como una base de desarrollo. La próxima entrada será acerca de Twig el motor de plantillas, montaremos un Layout básico para poder crear un pequeño backend. Había pensado en continuar con el ORM Doctrine pero me parece más lógico empezar por la visualización y más tarde poder montar pantallas funcionales donde hacer CRUD, listados, etc…