Etiqueta: Debugging

  • PHP me da error… ¿y ahora?

    PHP me da error… ¿y ahora?

    Cuando estás comenzando con php es muy común encontrarte ante alguna situación similar a esta:

    «por algún motivo no puedo insertar datos ni actualizar ni borrar.
    Me da un error en las lineas 51 y 52″

    «estoy queriendo insertar un string en una base de datos pero me da error, no me reconoce el formato de la hora»

    «estoy haciendo un select php con un where, pero no me muestra los resultados, me da error

    Seguramente te estarás preguntando qué puede estar pasando y más importante: ¿¿cómo resolverlo??

    Hay pocas cosas más desesperantes que probar y probar y que nada funcione…

    Te tengo buenas noticias: la solución está más cerca de lo que parece.

    Lo primero que hay que hacer en una situación como esta es respirar hondo, tal vez dar una pequeña vuelta alrededor del escritorio y volver con la cabeza más fresca.

    Una vez pasada la desesperación tenés que comprender algo fundamental:

    No todos los errores son iguales

    Con esto quiero decir que «me da error» ayuda poco cuando se trata de encontrar una solución.

    Hay que comprender la causa del problema para avanzar.

    Es como cuando vas al médico: si todo lo que el profesional sabe es que te sentís mal es difícil que te pueda curar.

    Cómo encontrar la causa del error

    Lo primero que necesitás es comprender exactamente cuál es el error.

    Usualmente, cuando un programa no completa su ejecución, en algún lado se guarda información de qué fue lo que sucedió.

    Dependiendo de la configuración es posible que esa información esté directo frente a tus ojos, a través de un mensaje de error en la pantalla:

    O que se esconda detrás de un mensaje algo genérico como:

    En este caso no significa que esta es toda la información que vas a obtener, sólo que vas a tener que buscar un poco más, probablemente en los archivos de log del servidor web.

    Los mensajes pueden parecer un poco difíciles de leer e interpretar, especialmente si no sabés inglés, pero si les prestás atención notarás que son muy informativos.

    Cerca del 80% de los errores de php se pueden comprender simplemente leyendo el mensaje. Compartir en X

    Veamos algunos ejemplos.

    Call to undefined function

    Fatal error: Uncaught Error: Call to undefined function sqlsrv_query() in /home/mauro/Code/sunat/cambio.php on line 4

    Desmenucemos un poco el mensaje.

    La primera parte dice Fatal error.

    Suena feo ¿no? Error fatal… tranquilo, no se murió nadie, simplemente significa que se ha producido un error que impide que el script siga ejecutándose.

    Luego vemos que dice Uncaught error.

    Uncaught significa «no atrapado», lo que da una idea de que los errores pueden atraparse… tema un poco avanzado para este momento, por lo pronto dejémoslo de lado.

    Y ahora sí llegamos a la parte jugosa: Call to undefined function sqlsrv_query().

    Este es realmente el problema: se está intentando llamar a una función que no ha sido definida aún (o al menos, el intérprete no la conoce).

    ¿Cuál es la función desconocida? sqlsrv_query.

    En php existen dos tipos de funciones:

    1. Propias del lenguaje (o de librerías escritas en C)
    2. Definidas por el programador (vos)

    El caso de sqlsrv_query es el primero, pero si intentaras llamar a una función tuya que el intérprete desconoce el problema sería el mismo.

    Así que aquí tenemos la primera pista.

    Luego tenemos otros dos datos muy importantes:

    in /home/mauro/Code/sunat/cambio.php

    Está indicando cuál es exactamente el archivo que se estaba ejecutando cuando el problema se presentó.

    Por último on line 4 te está indicando cuál fue exactamente la línea que disparó el error.

    Con todo esto tenés bastante de dónde agarrarte: sabés qué línea de qué archivo tenés que ir a mirar… no está mal, ¿cierto?

    Ahora vamos al caso específico.

    Si no sabés mucho sobre la función sqlsrv_query siempre tenés un aliado poderoso: el viejo y querido Google.

    Una búsqueda simple te va a dar estos resultados:

    Y ya que apareció un link a la página oficial de php.net, yo te diría que lo sigas a ver qué aparece :).

    Leyendo un poco vas a encontrar que la función sqlsrv_query pertenece a la extensión SQLSRV.

    Como el resto de las extensiones, puede ser habilitada o deshabilitada en la configuración de PHP (el famoso archivo php.ini).

    Así que una posibilidad es que la librería no esté instalada y/o habilitada.

    Sólo lo vas a saber mirando cómo está tu instalación de php.

    Undefined variable

    Notice: Undefined variable: ejecutar in C:\wamp\www\demo\php\insertar.php on line 19

    Acá lo que está diciendo el intérprete está diciendo:

    1. Que hay una variable no definida llamada ejecutar (Undefined variable: ejecutar)
    2. Que el problema aparece en la línea 19 del archivo C:\wamp\www\demo\php\insertar.php

    Momento.

    ¿No era que en PHP no hacía falta definir las variables?

    ¿En qué quedamos?

    Sin ver el código completo puedo inferir que en la línea 19 se está intentando usar esa variable antes de haberla asignado en algo como:

    <?php
    
    if ( $ejecutar == 'Otro ejemplo:algo' ) ...

    En php la definición y la primera asignación de una variable se producen en el mismo momento.

    Otro detalle interesante para notar es que el mensaje comienza con Notice (A diferencia del anterior que empezaba con Fatal Error).

    PHP tiene diferentes niveles de errores, algunos más graves que otros.

    Según el tipo específico del error se disparará (o no) alguna acción, como por ejemplo finalizar la ejecución del script ante la detección del error.

    Object of class … could not be converted to String

    Catchable fatal error: Object of class DateTime could not be converted to string

    Aquí lo que está sucediendo es que se está tratando de usar un objeto de tipo DateTime en el contexto de un string… y la clase DateTime no tiene un método __toString.

    Un código que podría dar este problema es:

    <?php
    
    echo 'Hoy es '.(new DateTime()).'!';

    Qué hacer cuando el mensaje de error no alcanza

    Claro que no siempre la vida es tan generosa… ¿Qué pasa cuando el mensaje de error no es suficiente?

    ¿Qué podés hacer?

    Bueno… sí, es una opción, pero existen algunas mejores opciones 🙂

    A veces sucede que encontrar la causa de un error requiere algo más que simplemente leer el código.

    Esto sucede, por ejemplo, cuando el programa falla a veces

    Tomemos un código como este:

    <?php
    
    $a = rand(1,3);
    
    switch ($a) {
    case 1:
            echo 'Hoy es: '.(new DateTime());
            break;
    case 2:
            echo $a;
    case 3:
            echo $a * 2;
    }
    
    echo PHP_EOL;

    Estadísticamente este programa debería fallar una de cada 3 veces que es ejecutado.

    ¿Cuándo? Cada vez que $a == 1.

    Y ¿cómo podés saber cuánto vale $a en cada ejecución?

    Necesitás alguna herramienta que te permita ver su contenido.

    Existen diferentes opciones:

    Pero la realidad es que la mejor alternativa es no usar ninguna de ellas, si no más bien un debugger.

    Qué es un debugger

    Un debugger es un programa que te permite ejecutar tu código paso-a-paso.

    Es decir, en lugar de correr el programa entero y, recién entonces, ver la salida y tratar de entender qué valor tenía cada variable al momento de la ejecución, con el debugger podés ver el contenido de tus variables en tiempo real.

    Los más populares para PHP son

    Si querés conocer más sobre cómo usar un debugger en tus aplicaciones podés seguir leyendo acá.

    Si PHP te da errores no corras

    La próxima vez que te encuentres ante un error de PHP (Y creeme, te va a pasar), no te desesperes.

    Tomá un poco de aire y hacé un pequeño esfuerzo por comprender el mensaje de error que estás viendo.

    Te sorprenderá la cantidad de veces que no necesitarás más para subsanar el problema 🙂

  • Cómo debuggear un webservice hecho con PHP

    Cómo debuggear un webservice hecho con PHP

    Un WebService no es, en escencia, muy diferente de otro tipo de aplicación web, sin embargo, existen ciertas particularidades que lo hacen ligeramente más dificultoso a la hora de afinar los detalles.

    Por qué es complejo debuggear un WebService

    La primera fuente de complejidad reside en el hecho de que un servicio web no está pensado para ser accedido a través de un navegador web (A diferencia de una aplicación común).

    Esto implica que será necesario contar con algún mecanismo que simule un consumidor (El cual, cuando esté implementado el servicio, será otra aplicación).

    Cómo simular una petición a un WebService

    Una herramienta muy útil para esto es cURL.

    A través de cURL podemos generar peticiones HTTP tan complejas como se requiera y, de ese modo, simular una interacción real.

    Por ejemplo:

    curl -H 'Api-Key: 12345abcd' -d '{ "param1": "valor1", "param2": "valor2" }' -X 'PUT' http://localhost:8000/ws

    Estaremos enviando el texto json { "param1": "valor1", "param2": "valor2" } a través del método PUT a nuestro servicio, el cual estará escuchando en el puerto 8000 de nuestra computadora.

    A su vez, esta petición estará precedida por el encabezado 'Api-Key: 12345abcd'

    No está mal, ¿cierto? Claro que para usarla requieres:

    1. Tenerla instalada (Ningún problema si usas Linux o Mac)
    2. Tener algo de familiaridad con la consola

    Existen otras herramientas gráficas para lograr el mismo resultado como PostMan o AdvancedRestClient o incluso alguna incorporada a los IDE.

    Por ejemplo, en phpStorm disponemos de un cliente HTTP incorporado:

    Utilizando una de estas herramientas es sencillo enviar pedidos a nuestro servidor como lo haría un cliente real.

    Cómo debugear un pedido HTTP

    Claro que una vez que recibimos el pedido empieza el verdadero trabajo de debugging.

    De lo que se trata, como siempre, es de comprender por qué nuestra aplicación (En este caso nuestro WebService) no está respondiendo como debería.

    Para ello es fundamental conocer qué sucede en cada momento y qué contenido tienen las variables a medida que el servidor genera la respuesta.

    PHP nos ofrece varias posibilidades (funciones como var_dump, echo o similares) pero estas tienen un problema: no son inocuas. Es decir, el resultado que obtendremos no será el mismo que si no estuvieran allí.

    Otro modo algo menos intrusivo es utilizar la función error_log.

    Mediante esta función podremos dejar un registro de qué sucedía en el servidor mientras se procesaba la petición (incluyendo el valor que tenían las variables).

    Esto puede funcionar aunque no es muy eficiente que digamos 😉

    ¿Entonces qué podemos hacer?

    Lo ideal es utilizar una herramienta especial para debugging: un debugger (Qué sorpresa, ¿no?).

    En el mundo de PHP existen varias opciones, la más popular es xDebug.

    Si tenés dudas respecto de cómo usarla te recomiendo este artículo (o este video si lo preferís).

    Así que ahora no quedan excusas para seguir sufriendo para crear tus propios WebServices 🙂

  • Cómo ser el primero en enterarse de los errores de tu aplicación web

    Cómo ser el primero en enterarse de los errores de tu aplicación web

    Cuántas veces te pasó que te mande un mail un cliente (o peor, te llame por teléfono) para decirte que la aplicación que pusiste en producción hace más de una semana acaba de dar uno de esos errores inentendibles:

    Y vos, estando en cualquier otro tema tenés que buscar en lo más recóndito de la memoria para recordar de qué se trataba y contestar algo medianamente coherente…

    ¿No sería genial poder decirle a tu cliente apenas atender: «Sí, ya sé, hubo un problema con la app, ya lo estoy viendo y enseguida lo tenés solucionado»?

    Hay varios puntos para señalar en este escenario:

    1. Más allá de que «queda feo» que un cliente vea un mensaje en inglés cuando el resto de la aplicación está en castellano (o que vea una pantalla con fondo blanco y despojada de todo branding), esto da a un potencial atacante información que no tiene por qué tener: que tu aplicación está desarrollada usando Symfony1 (y por lo tanto PHP).
    2. Se pierde un tiempo valioso buceando en los logs de errores hasta entender qué pudo haber pasado. Asumiendo que tales logs existan, claro.

    Algo que mejora tanto la seguridad de la aplicación como la experiencia del usuario es simplemente contar una página de error personalizado:

    Fácilmente puede especificarse qué hacer en caso de que la respuesta a enviar no sea un código 200 usando configuración de Apache, NginX o el webserver que sea.

    En este post te voy a mostrar cómo lograrlo sin tocar nada desde la infraestructura, si no todo dentro de tu propio proyecto.

    Lo voy a hacer usando el framework Symfony. Si estás usando otro no va a ser tan literal el tema, pero las ideas te servirán igual:

    En Symfony los errores HTTP se manejan mediante Excepciones, para cada tipo de error existe un template que se utiliza para generar la respuesta a enviar.

    Todo esto en realidad no es de Symfony si no de Twig (el motor de templating que viene junto con el framework).

    Dentro del código de una aplicación Symfony típica vas a encontrar un directorio vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Resources/views/Exception/ donde se encuentran los templates usados en caso de error.

    Como en cualquier otro caso en que necesites modificar comportamientos estándar de los bundles, lo más conveniente es crear tu propia copia de los archivos originales dentro del directorio app/Resources de tu aplicación. En este caso sería el directorio app/resources/TwigBundle y dentro de él views/Exception/

    Hasta aquí resolvimos el problema de lo que el usuario ve pero… ¡todavía sos el último en enterarse de lo que pasó!

    Bien… ¿cómo podrías enterarte de algo que está sucediendo en el servidor sin estar pegado al log de errores 24×7?

    ¿Qué tal una alerta por email?

    Lo primero que podrías pensar es en codear tu propio mecanismo de envío de correos ante la aparición de un error… y por ahí hasta lo lograrías. Pero, como nos gusta decir por aquí, ¿para qué reinventar la rueda?

    ¿Te acordás de lo que te conté sobre Monolog? Vamos a ver cómo se puede usar una de sus características más interesantes: la composición de loggers (o handlers en terminología Monolog).

    De lo que se trata acá no es ni de reemplazar el logging tradicional por el mail ni de llenarte la casilla de mensajes crípticos, si no de tener lo mejor de ambos mundos (Una alerta a tiempo con la información necesaria para reaccionar y los logs completos donde deben estar).

    Una forma de lograr esto es usando inteligentemente la configuración Monolog y de SwiftMailer. Estas configuraciones las encontrás en el archivo app/config/config.yml(y por supuesto en sus hijos config_dev.yml y config_prod.yml).

    Del lado de Monolog debería quedar algo como:

    monolog:
        handlers:
            main:
                type:         fingers_crossed
                action_level: error
                handler:      grouped
            grouped:
                type:       group
                members:    [streamed, deduplicated]
            streamed:
                type:  stream
                path:  %kernel.logs_dir%/%kernel.environment%.log
                level: debug
            deduplicated:
                type: deduplication
                handler: swift
            swift:
                type:           swift_mailer
                from_email:     app@dominio.com
                to_email:       mauro.chojrin@leewayweb.com
                subject:        "App::Error_Exception"
                level:          error
                formatter:  monolog.formatter.html
                content_type: text/html
            console:
                type:  console

    Lo que estamos haciendo acá es decirle a Monolog (a través del sistema de configuración de Symfony) que cada mensaje debe ser procesado por el handler grouped que, como podrás ver, es de tipo group (Una forma muy elegante de hablar de un conjunto de loggers).

    El primero de esos loggers es el clásico: archivo de texto local (Se podría hacer algo más complejo si tuvieses que sincronizar logs de diferentes servidores, pero lo dejamos para otro momento).

    El segundo logger es más interesante: deduplicated (Una suerte de filtro para no llenarte la casilla con el mismo mensaje enviado 1000 veces). Este a su vez usa un handler propio: swift.

    El handler swift es el que efectivamente se encarga de realizar el envío del error a la casilla que le indiques… en realidad, de lo que se encarga es de pedirle a SwiftMailer que haga todo eso.

    Y ahí es donde entra en juego la configuración propia de SwiftMailer… después de todo, esto tampoco es mágico.

    Lo más importante acá es a través de qué medio se va a realizar el envío, es decir qué tipo de transport vas a querer usar.

    En mi ejemplo usé una cuenta de Gmail creada especialmente para esta aplicación (Total son gratis y andan muy bien :)).

    La configuración al final es esta:

    swiftmailer:
        host: smtp.gmail.com
        username:  '%mailer_user%'
        password:  '%mailer_password%'
        encryption: ssl
        port: 465

    Al usar el username '%mailer_user%' y el password '%mailer_password%' lo que estoy diciendo es que los valores de esta configuración se obtengan del archivo app/config/parameters.yml. Como este archivo es específico para cada entorno donde la aplicación esté desplegada (léase: ¡no lo versiones!), esta configuración permite usar diferentes cuentas para desarrollo y para producción.

    Lo único que debés tener en cuenta si vas a usar Gmail (En lugar de algún otro SMTP público) es que vas a tener que permitir el acceso a la cuenta a aplicaciones no seguras.

    Por último, para recibir un mensaje que se vea así:

    En lugar de así:

    Se usan las líneas:

    formatter: monolog.formatter.html

    content_type: text/html

    De la configuración de Monolog.

    Y ahora sí, todo listo, si llega a producirse un error inesperado, el mismo sistema te va a avisar con un email bien formateado.

    Y si todavía querés llevar el tema a un nivel superior, una herramienta sumamente interesante que te recomiendo usar es Honeybadger.

    Con Honeybadger podés tener un panel de administración donde se centralicen todos los problemas u otros mensajes que quieras que tu aplicación envíe.

    De esa forma, la gestión de los problemas puede hacerse todavía más eficiente.

    Te recomiendo que le des una mirada.

  • Cómo hacer debug en php

    Cómo hacer debug en php

    Una de las tareas que más tiempo consume durante el desarrollo de una aplicación (web o no) es el debugging. Ese momento en que estás seguro de que todo anda bien, pero por las dudas… hay que probarlo.

    Y, como siempre, algo no sale exactamente como esperabas.

    Es entonces cuando te toca arremangarte, buscar una nueva taza de café, silenciar el teléfono y hurgar.

    El modo «común» de encarar esta tarea en el universo php es usar mucho la función var_dump (o sus parientes cercanos: print_r, echo, etc…).

    La verdad es que, si bien eso puede funcionar, está bastante lejos de ser óptimo.

    Herramientas de debugging PHP

    Una herramienta que vengo usando hace bastante y que ha sido el día y la noche en mi trabajo es xdebug.

    Se trata de un complemento para php que permite, entre otras cosas:

    • Ejecutar el código paso por paso
    • Evaluar el contenido de las variables en tiempo real
    • Establecer puntos de corte

    Históricamente instalarlo (y dejarlo funcionando!) era una tarea sólo apta para valientes… hoy en día, por suerte, las cosas son mucho más simples.

    Para usarlo de una forma cómoda y práctica necesitarás a su vez un IDE.

    En este caso, te mostraré cómo se hace con PhpStorm pero realmente es muy similar en otros.

    Cómo configurar PHPStorm para usar XDebug

    Desde dentro del IDE podés lanzar tu aplicación usando el botón de play (El triángulo verde):

    Lo que ves a la izquierda (En este caso app.php) es el nombre de la configuración de ejecución (Un concepto propio de PhpStorm que define un poco qué es lo que esperás que haga el IDE cuando hagas click en el botón).

    Lo primero que tenés que conocer entonces es este cuadro de diálogo (Al que accedés al editar la configuración de debugging):

    A la izquierda tenés todas las posibles configuraciones base (Debuggear una aplicación web, un script de CLI, etc…).

    A la derecha los detalles particulares. Lo más importante es configurar correctamente el servidor:

    Dentro de este cuadro tenés que mirar especialmente los campos:

    • Host: El servidor donde está desplegada tu aplicación (Si usás Xampp o algo así será localhost, si no puede ser la IP de la computadora donde esté tu código, dependerá de tu entorno particular el valor que debés ingresar).
    • Port: El puerto donde está escuchando el servidor web al que te vas a conectar (Por lo general será el 80)
    • Debugger: Qué software de debugging usarás (Los estándares son XDebug y Zend Debugger, si no usás ZendFramework te recomiendo usar el primero).

    Qué son los path mappings

    Por último, muy importante es configurar correctamente los mapeos de directorios. Este concepto puede solar un poco extraño al principio, pero es bastante claro: se trata de indicar dónde está, en el sistema de archivos local, el código que se está ejecutando en el servidor.

    Para entenderlo un poco mejor: el código que estás viendo en tu IDE está guardado en tu disco local (salvo que estés editando directamente sobre el servidor, algo que, salvo que uses una máquina virtual sería muy mala idea).

    El código que se ejecuta está en el webserver que tiene instalado el debugger.

    Por eso, para poder ir corriendo paso-a-paso el IDE necesita conocer qué archivo debe mostrar en base a la comunicación con el debugger.

    De eso precisamente es se tratan estos mapeos.

    Una vez terminada esta configuración basta volver a la barra de arriba

    Pero esta vez hacer click en el botón del «bichito» (Bug) y, si todo sale bien, verás una pantalla como esta en la parte de abajo de tu IDE:

    Y ahora se trata sólo de colocar breakpoints en algunos lugares estratégicos del código (Simplemente haciendo click a la izquieda de la línea seleccionada):

     Y recargar la página para que, cuando llegue a este lugar de la ejecución se vea algo como:

    En la parte del código la línea azul marca el código que está a punto de ejecutarse, pero la parte más interesante es la de abajo a la derecha, donde podés ver el contenido de las variables en tiempo real.

    De esta forma podés debuggear tu código sin temer dejar olvidado algún var_dump por ahí.

    ¿Te gustaría ver una sesión de debugging en vivo? Acá hay algo que puede interesarte.

  • 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?

  • Cómo debuggear un script de CLI con XDebug y PhpStorm

    Cómo debuggear un script de CLI con XDebug y PhpStorm

    Es muy común encontrarse con la necesidad de escribir scripts que deberán ser ejecutados por la línea de comandos dentro del contexto de una aplicación web, cronjobs por ejemplo.

    Hasta hace poco tenía muy claro cómo debuggear un script que ejecutaba a través del webserver, pero no había logrado (y francamente, no creía que fuera posible) debuggear un script que corría desde el CLI.

    Buscando un poco (y haciendo algo de prueba y error) finalmente lo logré.

    Si usás PhpStorm (y si no lo hacés aún, te lo recomiendo ampliamente) estos son los pasos que deberás seguir:

    Para empezar, asegurate de tener XDebug o ZendDebugger instalado en el entorno donde vas a correr tu script (En mi caso, usé una máquina virtual que ya tenía todo lo necesario).

    Después, la clave del tema está en el concepto de Run/Debug configuration:

    En este cuadro de diálogo:

    Debés indicar qué archivo vas a querer debuggear y, eventualmente, qué argumentos vas a querer pasarle al script al momento de ejecutar.

    Una vez que tengas la configuración armada… ¡listo! Ya podés ver la ejecución de tu script paso a paso como lo harías con una aplicación web.

    ¿Cómo debuggeas tus scripts?

  • 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!