Etiqueta: Symfony

  • Cómo manejar las excepciones en API Rest con Symfony

    Cómo manejar las excepciones en API Rest con Symfony

    Desarrollaste tu primer servicio web usando Symfony.

    Lo probaste por acá y por allá y funciona todo… salvo cuando no es así.

    Justo hubo un caso de esos muy extraños en que el registro que buscabas no tiene exactamente todos los campos solicitados… cosas que pasan en el desarrollo de software.

    Nada es grave, ¿no? Al final sólo se trata de enviar al cliente una respuesta adecuada para que sea él quien se encargue de resolver el problema y acompañarla del código de error HTTP correspondiente.

    A lo sumo, agregar una capa de Logging para poder analizar el tema cuando sea posible.

    La idea es bastante simple pero la pregunta es cómo hacerlo correctamente dentro aprovechando al máximo las capacidades del framework Symfony.

    La forma simple

    Para hacer algo rápido no se requiere mucho, basta con atrapar la excepción dentro del controlador en cuestión, generar una respuesta JSON, asignarle el código HTTP que corresponda y enviarla.

    Algo así:

    #[Route('/dangerous', name: 'dangerous_action', methods: ['GET'])]
        public function dangerousAction(Request $request): JsonResponse
        {
            try {
                $this->exceptionThrowerMethod();
            } catch (\Exception $exception) {
                
                $response = new JsonResponse([
                    'message' => $exception->getMessage(),
                    'data' => [],
                    'errors' => []
                ]);
                
                $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
                
                return $response;
            }
        }

    El problema es que esto habrá que replicarlo en cada acción (o en cada controlador al menos).

    Usando algunos elementos de Symfony es posible lograr una solución mucho más elegante y escalable.

    La forma correcta

    Si la API en cuestión cuenta con múltiples end-points es conveniente estandarizar el formato de las respuestas que se enviarán, sean estas exitosas o fallidas.

    El mejor modo de lograr esta homogeneidad es, como siempre que se trata de software, centralizar la funcionalidad común en un único componente.

    En este caso vale la pena contar con una clase como esta:

    <?php
    
    namespace App\Responses;
    
    class APIResponse extends \Symfony\Component\HttpFoundation\JsonResponse
    {
        /**
         * ApiResponse constructor.
         *
         * @param string $message
         * @param mixed  $data
         * @param array  $errors
         * @param int    $status
         * @param array  $headers
         * @param bool   $json
         */
        public function __construct(string $message, $data = null, array $errors = [], int $status = 200, array $headers = [], bool $json = false)
        {
            parent::__construct([
                'message' => $message,
                'data' => $data,
                'errors' => $errors,
            ], $status, $headers, $json);
        }
    }

    Con esto cuentas con lo necesario para estandarizar las respuestas a las peticiones de la API… pero el problema que se menciona en la sección anterior persiste: ¿cómo utilizar esta clase para responder a las excepciones?

    La respuesta: usando el sistema de eventos de Symfony.

    Cada excepción que no es atrapada explícitamente termina generando un evento de tipo kernel.exception, el cual puede ser atrapado por un EventListener como este:

    <?php
    
    namespace App\EventListener;
    
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
    use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
    
    class ExceptionListener
    {
        public function onKernelException(GetResponseForExceptionEvent $event)
        {
            $exception = $event->getException();
            $request   = $event->getRequest();
            
            $response = $this->createApiResponse($exception);
            $event->setResponse($response);
        }
        
        private function createApiResponse(\Exception $exception)
        {
            $statusCode = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : Response::HTTP_INTERNAL_SERVER_ERROR;
            $errors     = [];
    
            return new APIResponse($exception->getMessage(), null, $errors, $statusCode);
        }
    }

    Con esta clase ya tienes definida la acción que debe tomarse ante una excepción y, combinándola con la clase definida en el paso anterior te aseguras de que la respuesta siempre tenga el mismo formato (Algo que los consumidores de tu API te agradecerán seguramente).

    Sólo falta un último paso: conectar esta clase con el EventDispatcher.

    Registrar un EventListener en Symfony

    Para lograr esto necesitas hacer un par de ajustes a la configuración de tu aplicación.

    Al final del archivo config/services.yaml debes agregar:

    App\EventListener\ExceptionListener:
            tags:
                - { name: kernel.event_listener, event: kernel.exception }

    Y ya está.

    Con esta configuración Symfony sabrá conectar el método onKernelException de tu clase App\EventListener\ExceptionListener.

    Si te quedan dudas de cómo es hace Symfony para establecer esta relación puedes leer este artículo.

    Para seguir investigando

    Si estás desarrollando o, mejor aún, pensando en desarrollar una API REST usando Symfony te sugiero que le des una mirada a API-Platform, seguro que te puede ahorrar muchos dolores de cabeza.

  • Cómo saber el controlador que maneja una ruta en Symfony

    Cómo saber el controlador que maneja una ruta en Symfony

    El framework Symfony es de lo mejorcito que tenemos en el mundillo de PHP (Personalmente es mi favorito por lejos).

    Para procesar un request se requiere un método de alguna clase (Un controlador).

    Para decidir cuál es el controlador al que se debe invocar al momento de responder al pedido de un usuario se utiliza un componente llamado Router.

    Este componente conoce el mapeo entre una URI y dicho controlador.

    Existen diversos modos de definir este mapeo (por ejemplo mediante annotations).

    Esto hace que, si una aplicación es grande, puede ser algo complejo encontrar cuál es exactamente el controlador que se esconde detrás de una URL.

    En este artículo te mostraré un pequeño truco para obtener esa información.

    Voy a asumir que estás en algún tipo de consola POSIX (Linux, BSD, Mac o algo como el WSL de Windows).

    Cómo debugear el mapa de ruteo de Symfony

    Algo que seguramente conoces si usas Symfony es el poderoso comando (¿o debería llamarle meta-comando?) console.

    A través de esta interface es posible realizar una gran cantidad de tareas (e incluso implementar las tuyas).

    En esta ocasión quiero mostrarte un uso algo poco frecuente: el comando debug:router.

    Usando este comando puedes ver un listado completo de las rutas definidas en tu proyecto, sin importar si están definidas usando YAML, XML, PHP o annotations.

    Particularmente, si necesitas conocer la ruta que corresponde con una URL puedes usar la herramienta grep y un pipe, de esta forma:

    php bin/console debug:router -e prod | grep PARTE_URL

    Por ejemplo, digaos que necesitas conocer la ruta correspondiente a /admin/export/instant/.

    El comando que puedes utilizar es:

    php bin/console debug:router -e prod | grep export

    Y obtuve este resultado:

    Mapa de rutas definidas en la aplicación

    Una vez allí es fácil obtener el nombre de la ruta que buscas: oro_importexport_export_instant

    Cómo obtener el detalle de una ruta de Symfony

    Claro que con ese dato tienes apenas la punta del iceberg.

    Para obtener el detalle debes pasar como parámetro al comando el nombre de la ruta, por ejemplo:

    php bin/console debug:router -e prod oro_importexport_export_instant

    Y obtendrás un resultado similar a:

    Detalle de una ruta de Symfony

    Cómo encontrar el archivo donde está definido un controlador Symfony

    Y ahora que sabes cuál es el controlador es fácil llegar al archivo que lo define… ¿cierto? Pues… depende.

    Como decía, en el supuesto de una aplicación de mediana o gran envergadura, es probable que estés utilizando muchos bundles, con lo cual el controlador puede estar definido en varios lugares.

    Una forma simple de llegar a este dato es usar el comando find, por ejemplo:

    find vendor -type f  -name ImportExportController.php

    Y ahora sí, combinando esta información con la obtenida en el comando anterior es posible ver el código exacto del método que responde a esta petición.

    Nota al pié: este último paso puede simplificarse mucho si utilizas algún buen IDE para PHP 😉

  • Qué se necesita para desarrollar usando Symfony en Windows

    Qué se necesita para desarrollar usando Symfony en Windows

    Personalmente, es algo que preferiría evitar pero… si no queda opción, hay que hacerlo funcionar 🙂

    Algunos problemas que vamos a tener que resolver para tener un entorno de desarrollo medianamente cómodo son:

    1. Contar con PHP
    2. Contar con composer
    3. Contar con git
    4. Contar con algún IDE

    Todas estas cosas en Linux son prácticamente triviales (Especialmente si usás Ubuntu o algún derivado) pero en Windows son un poquito más difíciles de conseguir.

    Alternativamente podés usar un entorno virtualizado, pero si la única opción es un entorno local en Windows, acá van las instrucciones:

    Instalando PHP en Windows

    Instalar PHP en Windows no es una tarea extremadamente sencilla… para empezar debes descargar el ejecutable adecuado para tu S.O. (Según sea 32 o 64 bits). Eso lo podés hacer desde acá.

    El detalle es que, para que php funcione necesitas tener el redistribuible de Visual C++ correspondiente (!).

    Lo podés descargar de acá (Tené cuidado de descargar la versión correcta para tu Windows).

    Una vez esté todo instalado, para asegurarte de que funciona abrí una terminal en C:\php (O donde hayas descargado y descomprimido el php) y ejecutá el comando php -v.

    Deberías ver algo como:

    Pantalla de línea de comandos de Windows mostrando PHP funcionando

    Una opción alternativa (y que puede resultar más simple) es usar alguno que ya tenga todo incorporado (PHP, MySQL, etc…) como XAMPP o Laragon.

    A los efectos de este artículo asumiré que usamos el camino de sólo instalar PHP (Que, en definitiva es lo único que necesitamos por el momento).

    Instalando composer en Windows

    Conseguir en composer es bastante simple. Te lo podés bajar de acá y se instala como cualquier otro programa de Windows.

    Lo único importante aquí es ingresar la ruta del php.exe que hayas instalado en el paso anterior (Si hay uno solo el mismo instalador lo reconocerá):

    Selección del ejecutable de PHP

    Para ver que todo esté en su lugar, abrí una consola de Windows (Comando cmd) y tipeá composer. Deberías ver algo como:

    Composer desde la terminal de Windows

    Esto sucede porque el instalador de Composer ha modificado la variable de entorno PATH, de modo de que, tanto el comando composer como el propio php sean accesibles desde cualquier ubicación.

    Instalando git en Windows

    Algo bastante similar es la instalación de git para Windows. Lo bajás de acá y, al instalarlo no olvides instalar git-bash (Una consola posix para usar desde tu Windows casi como si estuvieras en Linux).

    No estoy 100% seguro de esto, pero me juego a que está basado en cyg-win, así que si conocés cyg-win no hay mucha novedad en git-bash.

    Habilitando extensiones de php necesarias para Symfony

    Symfony requiere para funcionar que la extensión php-curl esté disponible.

    Con la instalación que bajaste seguramente esto se cumple, sólo que puede estar deshabilitada en el archivo php.ini.

    Lo que tenés que hace es abrir el archivo c:\php\php.ini con algún editor de texto y sacar los «;» del comienzo de las líneas.

    Contenido original del archivo php.ini

    Debe quedar así:

    Creando tu primer proyecto Symfony en Windows

    Con todo esto en su lugar podés crear tu primer proyecto Symfony:

    1. Abrí una consola git-bash
    2. Creá tu proyecto usando composer
      1. composer create-project symfony/website-skeleton my-project
      2. Esperá a que termine la descarga
    3. Probálo!
      1. cd my-project
      2. php bin/console server:run
      3. Abrí un navegador en http://localhost:8000
      4. Si ves una pantalla como esta

    ¡Está todo listo!

    Ahora sólo te queda empezar a codear 🙂

    Adelante!

    P.D.: Si preferís el video podés verlo acá

  • Cómo hacer un CRUD con Symfony e EasyAdmin

    Cómo hacer un CRUD con Symfony e EasyAdmin

    Me proponía escribir un artículo sobre lo bueno que es EasyAdmin, pero se me ocurró que una imagen vale más que 1000 palabras… Y un video más aún :). Así que armé este:

    Si lo disfrutaste y te quedaste con ganas de aprender más sobre Symfony el curso Introducción a Symfony Framework te puede ayudar.

  • Cómo definir la configuración de la sesión en Symfony

    Cómo definir la configuración de la sesión en Symfony

     

    Ante todo, una aclaración:

    PHP maneja las sesiones a través de cookies (Antiguamente también se podía propagar el ID de sesión vía URL, aunque es una práctica muy poco segura y, sinceramente, hace mucho que no lo veo).

    Bien, ahora… ¿qué cosas podrías querer cambiar de la configuración de la sesión? Varias.

    1. El nombre de la cookie
    2. El tiempo de duración
    3. El lugar donde se almacena la información del lado del servidor

    Sobre la segunda y la tercera, acá tenés un ejemplo de por qué querrías hacerlo 🙂

    Respecto de la primera, más que nada se trata de un tema de seguridad. Fijate esta captura de pantalla de la consola del navegador:

    El nombre PHPSESSID es el nombre por defecto que se le asigna a la cookie de sesión de una aplicación hecha en PHP, eso significa que, si te encontrás con una cookie con este nombre es altamente probable (Por no decir seguro) que del otro lado se encuentre una aplicación desarrollada en PHP.

    Ese tipo de información es super útil para un atacante, ya que al conocer qué software está corriendo el servidor se le facilita enormemente la tarea de buscar una forma de explotarlo…

    Bien, ahora que te convencí de que es una buena idea cambiar el nombre de la cookie, te voy a mostrar cómo hacerlo en Symfony:

    Es muy simple, se trata de agregar un par de líneas al archivo config.yml:

    framework:
        session:
            # http://symfony.com/doc/current/reference/configuration/framework.html#handler-id
            handler_id:  session.handler.native_file
            save_path:   "%kernel.root_dir%/../var/sessions/%kernel.environment%"
            cookie_lifetime: 28800
            name: MIAPPSID

    Se puede configurar todos los parámetros de la sesión mediante este mecanismo y después, por ejemplo, se puede modificar algunos valores para el entorno de desarrollo definiendo un archivo config_dev.yml con algo como:

    imports:
        - { resource: config.yml }
    
    framework:
        session:
            save_path:   "/var/lib/php/sessions"

    Listo. Ahora tu aplicación es un poco menos vulnerable que antes :).

    P.D.: Si querés incorporar un framework maduro como Symfony a tu herramental, el curso Desarrollo de Aplicaciones Web Profesionales con PHP te puede interesar.

  • Cómo funciona el conversor de parámetros de Symfony

    Cómo funciona el conversor de parámetros de Symfony

    Cada vez que conozco más del framework Symfony, más me gusta :).

    Esto que te voy a mostrar a continuación me pareció un acto de magia cuando me lo crucé por primera vez: el conversor de parámetros.

    En la mayoría de los Controllers (especialmente cuando se trata de CRUDs), se recibirá algún parámetro que será la clave para encontrar el objeto sobre el que se quiere realizar la operación, por ejemplo:

    public function showAction(Request $request)
    {
      $client = $this->getDoctrine()->getRepository('AppBundle:Client')->find($request->getParameter('id');
    
      if ( $client ) {
        ...
      } else {
        // 404
      }
    }

    Es muy común ver código de este tipo. De hecho, si lo miramos desde un poco lejos notaremos que hay una estructura en común en estas operaciones:

    1. Buscar el objeto
    2. Si se encontró, procesar normalmente
    3. Si no se encontró generar un error 404

    La idea del conversor de parámetros es simplificar este proceso recurriendo a un uso muy interesante de las annotations:

    /**
     * @Route("/client/{id}")
     * @ParamConverter("client", class="AppBundle:Client")
     */

    La magia se produce en la combinación de las annotations @Route y @ParamConverter.

    La primera define el patrón que debe seguir una URI para ser mapeada a esta acción, la segunda define cómo deben interpretarse los parámetros de modo de generar obejtos.

    En definitiva el código quedaría algo así como:

    /**
     * @Route("/client/{id}")
     * @ParamConverter("client", class="AppBundle:Client")
     */
    public function showAction(Client $client)
    {
        ...
    }

    Notá cómo se simplificó el método (Toda la parte de la búsqueda la realiza directamente el framework y el método sólo es llamado cuando efectivamente se obtuvo el objeto buscado).

    Pero además de ahorrar código (y dolores de cabeza varios), esta metodología tiene un efecto colateral más que interesante: hace mucho más simple el testing automatizado. ¿Por qué? Porque fuerza a armar métodos basados en al inyección de dependencias.

    Más allá del framework de testing que uses, podés imaginar que es muy simple hacer algo como:

    $controller = new ClientController();
    
    $controller->showAction( new Client() );

    Obviamente el new Client() puede ser reemplazado por un objeto creado a tu conveniencia, sin tener que entrar en enroscados tests con bases de datos espurias.

    Todo lo que leíste hasta acá es en realidad un caso particular de conversor de parámetros (El de Doctrine), pero existen más (¡Incluso podés definir los tuyos propios!).

    No es que vayas a pasarte el día inventando tus propios conversores de parámetros, pero es importante saber cómo funcionan las herramientas que uno usa para poder sacarles el máximo jugo posible.

    Si te interesa aprender más sobre este gran framework el curso Introducción a Symfony puede ayudarte.

  • Cómo debuggear un comando Symfony usando PhpStorm

    Cómo debuggear un comando Symfony usando PhpStorm

    En este post voy a mostrarte un caso algo particular de cómo debuggear un script que corre por línea de comandos.

    Se trata de debuggear un script hecho con el framework Symfony.

    La particularidad que tiene este escenario es que el código que escibiste (y que querés verificar), no es un archivo php común, si no que es una claseque extiende de ContainerAwareCommand, con lo cual, no es posible invocarla en forma directa.

    La ejecución de este comando requiere del paso por el script bin/console. Ahora, si te fijás su contenido notarás que no es más que un script php, con lo cual, podría ser debuggeado usando la configuración «estándar» de PhpStorm para estos casos…

    La idea en definitiva es simple, cuando ejecutás algo como:

    php bin/console miApp:miComando

    El script php que estás ejecutando es bin/console y «miApp:miComando» no es otra cosa que un argumento de CLI.

    Entonces precisamente, de esa forma es como tenés que configurar el IDE para que pueda ejecutar tu comando (Y puedas ponerle breakpoints, evaluar variables, etc…):

    En este ejemplo, el comando que yo uso (EnviarRecordatorios) recibe parámetros a su vez. Ningún problema, son más argumentos que se le pasan a bin/console.

    ¿De qué otra forma podrías debuggear comandos symfony?

  • Los bundles mínimos para un proyecto empresarial Symfony

    Los bundles mínimos para un proyecto empresarial Symfony

    Ultimamente me estuve entusiasmando bastante con un par de proyectos en los que estamos trabajando en Leeway (obviamente codeados en mi framework favorito Symfony :)) y me pareció interesante compartir algo de la experiencia.

    Symfony por sí mismo es un gran Framework cuando se trata de armar un sólido Back-End, pero la parte de front… es un poco floja para mi gusto. Afortunadamente, el diseño modular que tiene ha permitido que mucha gente colabore con excelentes bundles, entre ellos:

    MopaBootstrapBundle

    Lograr un frontend responsive sin saber casi nada de css es, en mi opinión, un sueño hecho realidad. Este bundle permite apalancarse en Twitter Bootstrap para lograr una UI muy profesional (y estándar).

    Ejemplo de una pantalla lograda:

    Más información acá

    AsseticBundle

    La librería Assetic provee una forma muy buena de manejar los recursos estáticos (js, css, etc…). Mopa funciona muy bien con Assetic para el pre-procesamiento de css (vía less/sass).

    Más información acá

    FOSUserBundle

    Es raro que una aplicación (especialmente una empresarial) no requiera manejar diferentes perfiles de usuario.

    FOSUserBundle provee todo el andamiaje necesario para tener listo un sistema de autenticación, con registro de usuarios, edición de perfiles y demás casi sin escribir código.

    KnpMenuBundle

    No puedo dejar de mencionar este paquete que nos provee una excelente funcionalidad para crear menúes de forma extremadamente simple:

    Para instrucciones de instalación y uso ver acá

    cspooSwiftmailerMailgunBundle

    Otra tarea sumamente común en aplicaciones web es el envío de correos electrónicos.

    Cuando la cantidad y/o frecuencia de los envíos es alta, la carga de trabajo que implica puede ser pesada para el servidor web donde corre nuestra aplicación, es por eso que siempre es una buena idea delegar esto en servicios especializados.

    De las opciones que conozco, la que más me gusta es MailGun (sobre todo la parte de los 10k envíos mensuales gratuitos :).

    Su API es super accesible y, lo mejor de todo, existe cspooSwiftmailerMailgunBundle que permite interactuar con el servicio de forma muy sencilla (La instalación puede ser un poco tortuosa, pero vale la pena).

    Incorporando estos cuatro bundles a tu proyecto podrás lograr con muy poco esfuerzo una aplicación de alto potencial.

    Y si querés levantar la vara un poco más, te recomiendo probar PetkoparaCrudGeneratorBundle.

    Este bundle provee un generador de CRUDS muy superior al que trae Doctrine. Basta ver una pantalla generada para enamorarse:

    Así que bueno… ¡ahora sólo te queda armar tus aplicaciones profesionales!

    ¿Tenés algún otro bundle que considerás imprescindible? ¡Dejá la referencia en los comentarios!

  • Una máquina virtual lista para PHP+Symfony2+XDebug

    Charlando con algunos amigos desarrolladores php surgió un tema que les estaba resultando complicado, así que decidí poner mi pequeño granito de arena (para ellos y para otros que tal vez estén pasando por lo mismo).

    Ya habíamos hablado del por qué usar una máquina virtual para proyectos PHP. Ya estaba claro que usar un framework es más conveniente que no usarlo (independientemente de cuál fuera) y les había comentado sobre mis herramientas favoritas de automatización (Ansible y Vagrant).

    Todos estábamos de acuerdo «en la teoría», pero a la hora de pasar a la práctica se veían algo frustrados por no poder lograr tener una máquina virtual que fuese fácil de usar y que soportara, entre otras cosas, el uso de XDebug.

    Lo que te voy a mostrar a continuación son los archivos de configuración que yo usé en el último proyecto que hice en php:

    El archivo playbook.yml:

    ---
    - hosts: all
     sudo: true
     tasks:
    
    - name: create /var/www
     file: path=/var/www state=directory
    
    - name: create site symlink
     file: src=/vagrant dest=/var/www/site state=link
     notify: restart apache
    
    - name: install misc packages
     apt: name={{ item }} state=latest update_cache=true
     with_items:
     - ruby2.0
     - ruby2.0-dev
     - git
     - curl
     - unzip
     - vim
    
    - name: Symlink exists for Ruby 2.0
     file: src=/usr/bin/ruby2.0 dest=/usr/local/bin/ruby state=link
    
    - name: Symlink exists for Ruby Gems 2.0
     file: src=/usr/bin/gem2.0 dest=/usr/local/bin/gem state=link
    
    - name: install language packs for locale support
     apt: name={{ item }} state=latest
     with_items:
     - language-pack-de-base
     - language-pack-es-base
    
    # Apache2
    
    - name: ensure apache is installed
     apt: name=apache2 state=present
    
    - name: make sure apache is running
     action: service name=apache2 state=started enabled=true
    
    - file: src=/etc/apache2/mods-available/rewrite.load dest=/etc/apache2/mods-enabled/rewrite.load state=link
     notify: restart apache
    
    - file: src=/etc/apache2/mods-available/headers.load dest=/etc/apache2/mods-enabled/headers.load state=link
     notify: restart apache
    
    - copy: src=/vagrant/ansible/templates/site.conf dest=/etc/apache2/sites-available/site.conf remote_src=true
     notify: restart apache
    
    - file: src=/etc/apache2/sites-available/site.conf dest=/etc/apache2/sites-enabled/site.conf state=link
     notify: restart apache
    
    - file: path=/etc/apache2/sites-enabled/000-default.conf state=absent
     notify: restart apache
    
    - file: path=/etc/apache2/conf.d state=directory
    
    - copy: src=/vagrant/ansible/templates/fqdn.conf dest=/etc/apache2/conf.d/fqdn.conf remote_src=true
     notify: restart apache
    
    - copy: src=/vagrant/ansible/templates/nosendfile.conf dest=/etc/apache2/conf.d/nosendfile.conf remote_src=true
     notify: restart apache
    
    # MySQL
    
    - name: install MySQL
     apt: name={{ item }} state=latest
     with_items:
     - mysql-server
     - mysql-client
     - python-mysqldb
    
    - name: add mysql user
     mysql_user: name=vagrant
     host={{ item }}
     password=vagrant priv=*.*:ALL,GRANT
     login_user=root
     login_password=
     with_items:
     - '%'
     - localhost
    
    - name: create mysql databases
     mysql_db: name={{ item }}
     state=present
     with_items:
     - site_development
     - site_development_stats
     - site_testing
     - site_testing_stats
    
    - file: path=/etc/mysql/conf.d state=directory
     - name: Set MySQL number of connections
     copy: src=/vagrant/ansible/templates/max_connections.cnf dest=/etc/mysql/conf.d/max_connections.cnf remote_src=true
     notify: restart mysql
    
    - name: Install mysql command line client configuration file
     copy: src=/vagrant/ansible/templates/my.cnf dest=/home/vagrant/.my.cnf owner=vagrant group=vagrant remote_src=true
    
    # PHP
    
    - name: add php5.6 ppa
     apt_repository: repo='ppa:ondrej/php'
    
    - name: install PHP5.6 packages
     apt: name={{ item }} state=latest
     with_items:
     - php5.6
     - libapache2-mod-php5.6
     - php5.6-cli
     - php5.6-dev
     - php5.6-mysql
     - php-pear
     - php5.6-mcrypt
     - php5.6-gd
     - php5.6-curl
     - php5.6-xdebug
     - php5.6-memcache
     - php5.6-memcached
     - php5.6-readline
     - php5.6-xml
     - php5.6-mbstring
     - php5.6-zip
    
    - file: path=/etc/php5.6/conf.d state=directory
     - file: path=/etc/php5.6/cli/conf.d state=directory
     - file: path=/etc/php5.6/apache2/conf.d state=directory
    
    - copy: src=/vagrant/ansible/templates/php-site.ini dest=/etc/php5.6/conf.d/php-site.ini remote_src=true
     notify: restart apache
    
    - name: configure xdebug
     copy: src=templates/xdebug.ini dest=/etc/php/5.6/mods-available/xdebug.ini
     notify: restart apache
    
    - name: symlink common php configuration for cli handler
     file: src=/etc/php5.6/conf.d/php-site.ini dest=/etc/php5.6/cli/conf.d/php-site.ini state=link
     notify: restart apache
    
    - name: symlink common php configuration for apache2 handler
     file: src=/etc/php5.6/conf.d/php-site.ini dest=/etc/php5.6/apache2/conf.d/php-site.ini state=link
     notify: restart apache
    
    # phpmyadmin
    
    - name: install phpmyadmin
     apt: name=phpmyadmin state=latest
    
    # Assets compilation
    
    - name: add nodejs ppa
     apt_repository: repo='ppa:chris-lea/node.js'
    
    - name: install nodejs
     apt: name=nodejs state=latest
    
    # Set up site
    
    - file: src=/vagrant dest=/var/www/site state=link
     - file: path={{ item }} owner=vagrant group=vagrant mode=0777 state=directory
     with_items:
     - /var/cache/site
     - /var/cache/site/cache
     - /var/cache/site/clockwork
     - /var/cache/site/logs
     - /var/cache/site/meta
     - /var/cache/site/sessions
     - /var/cache/site/views
    
    - name: ensure once more that 000-default.conf is deleted
     file: path=/etc/apache2/sites-enabled/000-default.conf state=absent
     notify: restart apache
    
    - name: ensure that phpmyadmin's stock config is deleted
     file: path=/etc/apache2/conf.d/phpmyadmin.conf state=absent
    
    - name: set proper permissions for app/reports directory
     file: path=/vagrant/app/reports group=www-data owner=vagrant mode=0775 state=directory
    
    # Common stuff
    
    - name: Install Composer
     shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer creates=/usr/local/bin/composer
    
    handlers:
     - name: restart apache
     action: service name=apache2 state=restarted
     - name: restart mysql
     action: service name=mysql state=restarted
     - name: restart beanstalkd
     action: service name=beanstalkd state=restarted

    Los archivos templates (deben estar en el directorio ansible/templates dentro de tu proyecto):

    site.conf:

    <VirtualHost *:80>
     ServerName myApp
     DocumentRoot /var/www/site/web
    
    <Directory />
     Options FollowSymLinks
     AllowOverride None
     </Directory>
    
    <Directory /var/www/site/web>
     DirectoryIndex app_dev.php
     Options Indexes FollowSymLinks MultiViews
     AllowOverride All
     Order allow,deny
     allow from all
     </Directory>
    
    ErrorLog /var/log/apache2/error.log
     LogLevel warn
     CustomLog /var/log/apache2/access.log combined
    
    ## enable phpmyadmin
    
    Alias /phpmyadmin /usr/share/phpmyadmin
    
    <Directory /usr/share/phpmyadmin>
     Options FollowSymLinks
     DirectoryIndex index.php
    
    <IfModule mod_php5.c>
     AddType application/x-httpd-php .php
    
    php_flag magic_quotes_gpc Off
     php_flag track_vars On
     php_flag register_globals Off
     php_admin_flag allow_url_fopen Off
     php_value include_path .
     php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
     php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/
     </IfModule>
    
    </Directory>
    </VirtualHost>

    fqdn.conf

    ServerName Localhost

    nosendfile.conf

    EnableSendfile off

    max_connections.cnf

    [mysqld]
    max_connections = 400

    my.cnf

    [client]
    user=vagrant
    password=vagrant

    php-site.ini

    max_execution_time = 30000
    memory_limit = 512M
    display_errors = On
    disable_functions =

    xdebug.ini

    zend_extension=xdebug.so
    xdebug.remote_enable=On
    xdebug.remote_connect_back=On

    Para iniciar el proyecto, basta con copiar y pegar esto en un archivo llamado Vagrantfile (en la raíz de tu proyecto):

    # -*- mode: ruby -*-
     # vi: set ft=ruby :
    
    # All Vagrant configuration is done below. The "2" in Vagrant.configure
     # configures the configuration version (we support older styles for
     # backwards compatibility). Please don't change it unless you know what
     # you're doing.
     Vagrant.configure("2") do |config|
     # The most common configuration options are documented and commented below.
     # For a complete reference, please see the online documentation at
     # https://docs.vagrantup.com.
    
    # Every Vagrant development environment requires a box. You can search for
     # boxes at https://atlas.hashicorp.com/search.
     config.vm.box = "ubuntu/trusty64"
    
    # Disable automatic box update checking. If you disable this, then
     # boxes will only be checked for updates when the user runs
     # `vagrant box outdated`. This is not recommended.
     # config.vm.box_check_update = false
    
    # Create a forwarded port mapping which allows access to a specific port
     # within the machine from a port on the host machine. In the example below,
     # accessing "localhost:8080" will access port 80 on the guest machine.
     config.vm.network "forwarded_port", guest: 80, host: 8080
    
    # Create a private network, which allows host-only access to the machine
     # using a specific IP.
     config.vm.network "private_network", ip: "192.168.33.10"
    
    # Create a public network, which generally matched to bridged network.
     # Bridged networks make the machine appear as another physical device on
     # your network.
     # config.vm.network "public_network"
    
    # Share an additional folder to the guest VM. The first argument is
     # the path on the host to the actual folder. The second argument is
     # the path on the guest to mount the folder. And the optional third
     # argument is a set of non-required options.
     # config.vm.synced_folder "../data", "/vagrant_data"
    
    # Provider-specific configuration so you can fine-tune various
    
    # backing providers for Vagrant. These expose provider-specific options.
     # Example for VirtualBox:
     #
     config.vm.provider "virtualbox" do |vb|
     # # Display the VirtualBox GUI when booting the machine
     # vb.gui = true
     #
     # # Customize the amount of memory on the VM:
     vb.memory = "2048"
     end
     #
     # View the documentation for the provider you are using for more
     # information on available options.
    
    # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
     # such as FTP and Heroku are also available. See the documentation at
     # https://docs.vagrantup.com/v2/push/atlas.html for more information.
     # config.push.define "atlas" do |push|
     # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
     # end
    
    # Enable provisioning with a shell script. Additional provisioners such as
     # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
     # documentation for more information about their specific syntax and use.
     # config.vm.provision "shell", inline: <<-SHELL
     # apt-get update
     # apt-get install -y apache2
     # SHELL
    
    #
     # Run Ansible from the Vagrant Host
     #
     config.vm.provision "ansible" do |ansible|
       ansible.playbook = "ansible/playbook.yml"
       ansible.verbose = "vv"
     end
    
    # Symfony needs to be able to write to it's cache, logs and sessions directory in var/
     config.vm.synced_folder "./var", "/vagrant/var",
       :owner => 'vagrant',
       :group => 'www-data',
       :mount_options => ["dmode=777","fmode=777"]
     end

    y ejecutar el comando (Obviamente, habiendo instalado Vagrant previamente…):

     vagrant up

    Y comienza la magia 🙂

    Un ratito después (Dependiendo de la conexión que tengas) vas a tener una máquina lista para desarrollar tu aplicación basada en Symfony2.

    Y ahora… ¡a codear!

    ¿Me olvidé de algo? ¡Avisame en los comentarios!