Categoría: Cómo hacer para…

Estos artículos te explicarán cómo resolver problemas específicos usando PHP

  • 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 consumir un WebService REST con PHP

    Cómo consumir un WebService REST con PHP

    La conexión de tus aplicaciones php con WebServices REST te permitirá ampliar tus capacidades apoyándote en servicios de grandes compañías.

    Los detalles específicos dependen de las definiciones que haya realizado el productor del servicio pero, para no encontrarte con sorpresas, es buena idea tener claras las bases.

    Repasemos los conceptos comunes a todo Servicio Web REST.

    Qué es un webservice

    Un webservice (o servicio web) es una forma de integrar aplicaciones web.

    Básicamente se trata de un servidor que expone parte de su funcionalidad para que sus clientes (que generalmente son otros servidores de diferentes aplicaciones) puedan utilizarlas.

    Las principales ventajas de usar web services son:

    1. La posibilidad de valernos de la capacidad instalada (¡y mantenida!) por terceros
    2. La facilidad para extender la funcionalidad de nuestra aplicación (Basta con realizar las llamadas al servicio web que deseamos)

    La principal desventaja de usar este enfoque es que dependemos de servicios de terceros que, salvo en casos muy puntuales, no podemos controlar.

    Ejemplos de webservices:

    1. Autenticación de usuarios vía Facebook, Google, Twitter, etc…
    2. Generación de mapas en tiempo real
    3. Consulta de cotizaciones de acciones

    Qué es RESTful

    RESTful es un modelo arquitectónico de software que plantea una suerte de volver a las fuentes.

    Su filosofía se basa en el poder del protocolo HTTP (Subyacente en toda aplicación web), el cual incluye los conceptos de verbo (GET, POST, DELETE, PUT), recurso, código de errores (404, 500, etc…) y demás elementos que, en teoría al menos, deberían alcanzar para modelar cualquier tipo de interacción cliente-servidor.

    Es muy común que en una aplicación RESTful los objetos o entidades del modelo de datos sean expuestos casi directamente, teniendo una URL específica para cada uno de ellos.

    Por ejemplo, en una aplicación diseñada para una institución educativa sería esperable encontrarse con una URL de tipo:

    http://miescuela.com/alumnos

    Al realizar una petición de tipo GET a esta URL se esperará recibir información de los alumnos de esa institución.

    A su vez, una URL del tipo:

    http://miescuela.com/alumnos/14

    Debería darme información del alumno cuyo id es 14.

    Existe mucha controversia respecto de qué es una API RESTful y qué no lo es…

    Sin entrar en mayor detalle, la idea es que, para consumir un servicio web basado en REST sólo se requiere conocer su URL (De nuevo, esto es en teoría… en la práctica se utilizan varias formas de modificación del pedido, como ser el envío de headers y demás).

    Siendo que se trata de un servicio RESTful, la comunicación entre cliente y servidor es sencilla (A diferencia de lo que es un WebService SOAP), se trata simplemente de enviar un pedido HTTP y procesar su respuesta.

    Cómo realizar peticiones REST con PHP

    Existen varias opciones disponibles, veamos las más comúnmente utilizadas:

    cURL

    La librería cURL permite realizar peticiones a servidores remotos.

    Si bien funciona, es un método de bastante bajo nivel, con sus pros y sus contras.

    Volviendo al ejemplo que te comentaba antes, una forma de hacer la llamada sería esta:

    <?php
    $ch = curl_init();
    
    curl_setopt($ch, CURLOPT_URL, "http://miescuela.com/alumnos");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $res = curl_exec($ch);
    
    curl_close($ch);

    En la variable $res quedará la respuesta del webservice (Asumiendo por supuesto que no haya habido ningún problema de conexión, transferencia, etc…).

    file_get_contents

    Una opción alternativa (y generalmente más conveniente) es el uso de la función file_get_contents.

    Esta función recibe como parámetro el nombre del archivo que se quiere leer y devuelve todo su contenido como un string.

    Si bien el uso normal de la función es la lectura de archivos locales, php en realidad entiende a los archivos como flujos. Por lo tanto, si está todo bien configurado, una URL puede usarse como nombre de un flujo.

    De este modo nos ahorramos muchas de las complicaciones propias de tratar con una interface de más bajo nivel (y, obviamente, perdemos cierto control, pero generalmente no es necesario hilar tan fino).

    El ejemplo sería:

    <?php
    $res = file_get_contents("http://miescuela.com/alumnos");

    Cómo se procesan los resultados

    Los resultados se procesan de acuerdo al formato de la respuesta.

    Usualmente los Servicios Web devuelven XML o JSON (Existen otras posibilidades, claro, pero estas son las más comunes).

    En el caso de tratarse de un XML, lo mejor es usar la biblioteca SimpleXML, si se trata de json, con json_decode será suficiente.

    Si se trata de otro formato habrá que estudiar el caso puntual, pero en última instancia, siempre se trata de procesar un string…

    Ejemplo de consumor de un WebService REST con PHP

    MercadoLibre tiene una API RESTful que permite interactuar con el sitio de forma simple.

    Veamos un ejemplo de una llamada de acceso público: la que nos da información básica de un usuario.

    La URL que se utiliza es https://api.mercadolibre.com/users/USERID/

    Por ejemplo https://api.mercadolibre.com/users/226384143/ retorna:

    {
      "id": 226384143,
      "nickname": "TETE9928972",
      "registration_date": "2016-08-25T11:36:00.000-04:00",
      "country_id": "AR",
      "address": {
        "city": "Palermo",
        "state": "AR-C"
      },
      "user_type": "normal",
      "tags": [
        "normal",
        "test_user",
        "user_info_verified"
      ],
      "logo": null,
      "points": 100,
      "site_id": "MLA",
      "permalink": "http://perfil.mercadolibre.com.ar/TETE9928972",
      "seller_reputation": {
        "level_id": null,
        "power_seller_status": null,
        "transactions": {
          "canceled": 0,
          "completed": 0,
          "period": "historic",
          "ratings": {
            "negative": 0,
            "neutral": 0,
            "positive": 0
          },
          "total": 0
        }
      },
      "buyer_reputation": {
        "tags": []
      },
      "status": {
        "site_status": "active"
      }
    }

    Un string JSON.

    El código completo para obtener esta información con php es:

    <?php
    
    echo file_get_contents('https://api.mercadolibre.com/users/226384143/');

    Claro que si quisiéramos hacer algo con esta información sería más conveniente hacer algo como:

    <?php
    
    $data = json_decode( file_get_contents('https://api.mercadolibre.com/users/226384143/'), true );
    
    echo $data['nickname'];

    ¡Listo! Ya estás en condiciones de integrar cualquier WebService RESTful en tu aplicación.

    Más información sobre WebServices con PHP

    Como te decía al comienzo, otro protocolo muy utilizado a la hora de conectar aplicaciones a través de Servicios Web es SOAP. Este protocolo es bastante más complejo que RESTful, principalmente porque se basa en intercambios de XML con un formato bastante particular.

    Puedes intentar leer e interpretar estos XML por tus propios medios aunque lo más recomendable es utilizar las herramientas que PHP ofrece para ello.

    De hecho, si te enfrentas a WebServices de basados en SOAP php tiene un soporte nativo muy completo.

  • Cómo usar CC y BCC con PHPMailer

    Cómo usar CC y BCC con PHPMailer

    ¿Qué es PHPMailer?

    Comencemos por el principio: PHPMailer es una librería que permite enviar emails desde PHP (Podés consultar algunas opciones acá).

    Si bien no es la única (De hecho, mi preferida es SwiftMailer), su principal ventaja es que suele estar disponible en entornos de hosting compartido, con lo cual, su uso es bastante popular.

    ¿Cómo se usa?

    Su uso es bastante simple: basta con crear una instancia de PHPMailer para tener acceso a una gran cantidad de funcionalidad:

    <?php
    use PHPMailer\PHPMailer\PHPMailer;
    use PHPMailer\PHPMailer\Exception;
    
    require_once 'vendor/autoload.php';
    
    $mail = new PHPMailer();
    
    $mail->isSMTP();                                      
    $mail->Host = 'mail.google.com';
    $mail->SMTPAuth = true;                               
    $mail->Username = 'usuario@gmail.com';                
    $mail->Password = 'miSuperPassword';                           
    $mail->SMTPSecure = 'tls';                            
    $mail->Port = 587;                                   
    
    $mail->setFrom('acedmy@leewayweb.com', 'Leeway Academy');
    $mail->addAddress('juan.perez@yahoo.com', 'Juan Perez'); 
    
    $mail->Subject = 'Este es el asunto';
    $mail->Body    = 'Este el cuerpo del mensaje';
    
    if(!$mail->send()) {
        echo 'No se pudo enviar el mensaje...'.$mail->ErrorInfo;
    } else {
        echo 'El mensaje se envió!';
    }

    Claro que, antes de poder usarla, la librería debe estar instalada… En este ejemplo vemos cómo se utiliza asumiendo que se ha usado composer para incorporarla al proyecto.

    Para agregar otros destinatarios en copia (CC) o copia oculta (BCC), simplemente debemos agregar estas líneas:

    $mail->addCC('copiado@hotmail.com');
    $mail->addBCC('copia_oculta@outlook.com');

    Antes de realizar el envío.

    Por útlimo, como siempre, no hay que olvidar el $mail->send().

    ¿Alguna pregunta? ¡Deja un comentario!

     

  • 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 enviar mails con formato usando SwiftMailer

    Cómo enviar mails con formato usando SwiftMailer

    Ya he comentado en otros artículos sobre las diferentes posibilidades para enviar emails usando PHP. De todas las que he probado hasta el momento, SwiftMailer es la que me resulta más cómoda y conveniente, sin embargo, he tenido algunas peleas a la hora de enviar mails con formato (es decir, mails que contengan HTML que necesito que sea entendido como tal y no como mero texto).

    La verdad es que es bastante simple lograrlo, sólo hay que conocer el método adecuado 🙂

    En general, lo que uno hace cuando quiere enviar un email es algo como esto:

    $message = (new Swift_Message())
      ->setSubject('Este es el asunto')
      ->setFrom(['mauro.chojrin@leewayweb.com' => 'Mauro Chojrin'])
      ->setTo(['destinatario@dominio.com' => 'Gran Amigo'])
      ->setBody('Este es el importante mensaje que quiero enviarte!!')
      ;

    El problema (si puede llamársele así) es que nos hemos olvidado que el mail acompaña a Internet desde sus inicios, con lo cual, el modo default de envío de correos es texto plano (Aunque muy poca gente lo utilice así hoy en día).

    Para enviar correos utilizando HTML se debe agregar una parte al correo que así lo especifique. Es decir, en lugar de usar setBody debemos usar addPart:

    $message = (new Swift_Message())
      ->setSubject('Este es el asunto')  ->addPart('<p>Este es el <strong>importante</strong> mensaje que quiero enviarte!!</p>')
      ->setFrom(['mauro.chojrin@leewayweb.com' => 'Mauro Chojrin'])
      ->setTo(['destinatario@dominio.com' => 'Gran Amigo'])
      ->addPart('<p>Este es el <strong>importante</strong> mensaje que quiero enviarte!!</p>')
      ;

    Si queremos ser muy puritanos, deberíamos incluir ambas versiones (La HTML y la de texto plano) para no perder a aquellas personas que no tienen habilitado el HTML en su cliente de correo (Aunque sinceramente, es muy raro que así sea y si queremos lograr un buen efecto visual, nada como el HTML, CSS y demás).

  • Cómo definir relaciones Cero-a-Uno con Doctrine

    Cómo definir relaciones Cero-a-Uno con Doctrine

    En general cuando uno comienza a estudiar Bases de Datos Relacionales se habla de un tipo de relación Uno-a-Uno que, en la práctica se usa muy poco. Sin embargo las relaciones tipo Cero-a-Uno tienen muchísimo sentido.

    Es el caso de que se quiera modelar un sistema donde una entidad es un caso especial de otra (Algo similar al concepto de herencia de POO), como por ejemplo la relación entre personas y actores:

    • Todos los actores son personas
    • No todas las personas son actores

    La forma de implementar esto en una base de datos relacional es poner un campo tipo clave foránea en la tabla hija, el cual a su vez tendrá un índice único (Para reforzar la cardinalidad de la relación).

    En el caso del ORM Doctrine, todo esto parte de la definición de la clase Entity.

    Pongo un ejemplo de un proyecto en el que trabajé: hay una clase User (De FOSUserBundle) y una clase Taller. Cada taller tiene un responsable, pero no todos los usuarios son responsables de algún taller.

    En la clase User está este código

    /**
     * @var Taller
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\Taller",mappedBy="responsable")
     */
    protected $taller;

    Y en la clase Taller este:

    /**
     * @var User
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="taller")
     */
     private $responsable;

    Como podés observar, ambas clases tienen una referencia a la otra, pero en la base de datos, sólo la tabla Taller tiene una referencia a User.

    Esto se debe a cómo están escritas las definiciones del vínculo OneToOne.

    Más específicamente, se debe a qué clase tiene el mappedBy y el inversedBy: El lado 1 de la relación (es decir, la entidad que siempre existirá) es el que debe llevar inversedBy.

    Si bien este tipo de relaciones no son lo más común, ciertamente viene bien conocerlas y la posibilidad de definirlas usando las herramientas de Doctrine ayuda mucho.

    ¿Alguna duda? ¡Dejala en un comentario!

  • Cómo usar el intérprete de PHP de una VM desde PhpStorm

    Cómo usar el intérprete de PHP de una VM desde PhpStorm

    Una práctica muy buena a la hora de programar (y ahorrarse dolores de cabeza) es el uso de máquinas virtuales.

    Muchos desarrolladores usan este esquema, sin embargo, también es bastante común que tengan sus propias versiones del software usado para ejecutar sus aplicaciones instalado en su máquina física (por ejemplo PHP).

    Si estás trabajando en varios proyectos a la vez, los cuales están deployados en servidores diversos, es muy probable que en cada VM tengas instalada una versión diferente de PHP (La misma que tenés en el servidor de producción), con lo cual, al menos una VM debe tener instalada una versión diferente de PHP que la que tenés instalada en tu máquina.

    Como usás una VM no deberías tener mayores dificultades a la hora de deployar, pero a la hora de desarrollar, también sería bueno que tu IDE estuviese ejecutando la misma versión de PHP que va a dar vida a tu código…

    Una característica muy interesante de mi IDE preferido (PhpStorm) es la posibilidad de analizar el código (mientras lo escribo) usando el intérprete de PHP que está en la VM (y no el que tiene el propio IDE o el que tengo instalado en mi computadora).

    Para configurarlo tenés que ir a la pantalla de settings:

    Y configurar el intérprete de línea de comandos (CLI Interpreter):

    Por defecto vas a ver a la izquierda los intérpretes que tengas instalados en tu computadora, pero podés agregar uno:

    Que puede ser accedido remotamente (Por ejemplo a través de Vagrant).

    Obviamente, para que esto funcione la VM de la que querés sacar el intérprete tiene que estar encendida…

    Y listo, con esto te asegurás de tener un IDE que entienda exactamente la versión de PHP que vas a usar en tu entorno de desarrollo y en producción.

    Si querés ver más detalles podés consultar acá.

  • 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 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 logear errores con PHP

    Cómo logear errores con PHP

    Por más esfuerzo que pongas, los usuarios se las arreglan para encontrar errores antes que vos.

    No sólo eso, lo más probable es que, al reportar un error (o intentar hacerlo), no tengan mucho más para decir que

    «No hice nada y el sistema se colgó»

    Evidentemente, no es un panorama muy alentador como punto de partida para encontrar una solución, ¿cierto?

    Que bueno sería saber exactamente qué estaba pasando en el momento en que se produjo el error…

    Precisamente, es ahí donde tener un buen log de errores puede hacer la diferencia entre perder el fin de semana y ser el héroe del día.

    Veamos entonces algunas técnicas para contar con esa información cuando se la necesita (y sí… tarde o temprano la vas a necesitar).

    La primera opción es casi obvia: guardar los logs mediante tu propio mecanismo (mucho fopen, fwrite o similares, etc…).

    Podés hacer esto, y hasta puede funcionar bien, pero representa un esfuerzo importante y una gran propensión a generar más errores que los que previene.

    Por ejemplo, hay que tener en cuenta que, si la cantidad de usuarios concurrentes es alta (como se espera en un entorno web) habrá problemas de disponibilidad del archivo donde se almacenan los logs, con lo cual probablemente tengas que implementar tu propio sistema de semáforos o algo así… un poco demasiado para lo que estás queriendo lograr, ¿no?

    Ojo, PHP te provee una función bastante interesante: error_log, que ya maneja toda esa complejidad… no está mal, ¿cierto?

    Pero podés encontrarte con casos aún más complejos, como ser un sistema distribuido que requiere de logs centralizados.

    Un caso típico es cuando se trata de muchos servidores detrás de un balanceador de carga:

    En este caso, te interesará que si hay errores en cualquiera de los servidores se tomen como un único error de la aplicación, con lo cual, los archivos de logs individuales de cada servidor tendrán poca relevancia.

    Nuevamente, aquí podés optar (entre otras) por:

    • Guardar los logs en un archivo compartido entre los servidores (por ejemplo por NFS o similar)… poco recomendable (mala performance, mala seguridad, etc…)
    • Guardar los logs en una base de datos compartida
    • Enviar los errores directamente por email

    Y aquí es donde te conviene empezar a pensar en usar alguna librería que maneje todo esto… como por ejemplo Monolog.

    Monolog es precisamente eso: una librería hecha especialmente para manejar procesos de logging complejos. Permite entre otras cosas diferenciar almacenamiento de logs en función de la gravedad de los sucesos (que vos también podés definir).

    Como para empezar a probarla podés instalarla usando composer:

    composer require monolog/monolog

    Y luego, la podés empezar a usar en tu aplicación con algo como:

    <?php
    
    require_once 'vendor/autoload.php';
    
    use Monolog\Level;
    use Monolog\Logger;
    use Monolog\Handler\StreamHandler;
    
    $log = new Logger('mi-canal');
    $log->pushHandler(new StreamHandler(__DIR__.'/mi-app.log', Level::Warning));
    
    $log->warning('Este es un warning');
    $log->error('Este es un error');

    Después de ejecutar este código, abrí el archivo mi-app.log y te encontrarás con algo como:

    [2025-01-08T08:44:02.872376+00:00] mi-canal.WARNING: Este es un warning [] []
    [2025-01-08T08:44:02.873235+00:00] mi-canal.ERROR: Este es un error [] []

    Una característica interesante de este tipo de logs es que son fácilmente filtrables usando una herramienta como grep. Por ejemplo, para buscar sólo los errores podés usar un comando como cat mi-app.log | grep ERROR para obtener:

    [2025-01-08T08:44:02.873235+00:00] mi-canal.ERROR: Este es un error [] []

    Así que dale, instalá monolog, configuralo y empezá a generar logs poderosos en tus aplicaciones.