Categoría: Herramientas

En estos artículos aprenderás sobre herramientas que harán más eficiente tu trabajo

  • Cómo testear los emails que envía tu aplicación

    Cómo testear los emails que envía tu aplicación

    ¿Alguna vez te llegó un correo similar a este?

    Es un hecho: los mails que genera y envía tu aplicación son una parte de ella y, como tal, deben ser testeados.

    En este artículo te mostraré algunas técnicas que te ayudarán a encarar estas pruebas

    Testear emails enviando a un único destinatario

    Una técnica simple es hacer que todos los correos lleguen siempre a un mismo destinatario (a vos).

    La ventaja de esto es que prácticamente no necesitás nada extra (Nada que no tengas digamos).

    El problema es que implementar esto puede implicar ensuciar el código con condicionales aquí y allí.

    Si usas un componente para el envío (Como el Mailer de Symfony), las cosas pueden ser más simples.

    Basta con especificar el destinatario único en el archivo de configuración de esta forma:

    # config/packages/dev/mailer.yaml
    framework:
        mailer:
            envelope:
                recipients: ['youremail@example.com']

    A partir de aquí, todos los correos salientes serán recibidos por un único destinatario independientemente de quién sea el «verdadero».

    Testear emails usando un servidor SMTP local

    Otra forma de realizar estas pruebas es instalar un servidor SMTP local y configurarlo de modo de no realizar ningún envío, si no encolarlos todos y luego consultar los envíos pendientes.

    Realizar esta configuración desde cero puede resultar bastante engorroso.

    Afortunadamente existen algunas herramientas que simplifican mucho todo este proceso.

    Una que me parece particularmente interesante es MailHog.

    Es bastante simple de instalar y cuenta con una interface web para consultar los correos que se han «enviado» a través de ella:

    Otra ventaja no menor de usar MailHog es que viene pre-instalado con el paquete Homestead de Laravel (Una excelente herramienta para usar una máquina virtual para tus desarrollos)

    Pero, por si no te convencí de usar MailHog aún, acá te dejo algunas alternativas:

  • Cómo usar Docker en proyectos PHP

    Cómo usar Docker en proyectos PHP

    Hace tiempo que vengo usando (¡y abogando por su uso!) máquinas virtuales para mis proyectos PHP.

    Hasta ahora me venía manejando con Vagrant y debo decir que me ha dado unas cuantas satisfacciones.

    Sin embargo, hay algunos problemas derivados de su uso:

    1. Las VM se pueden volver muy pesadas
      • Ocupan mucho espacio en el disco
      • Son lentas de levantar
      • No es sencillo tener muchas corriendo a la par (Consumen muchos recursos de hardware)
    2. No es fácil asegurarme de que en Producción y en Desarrollo tengo exactamente el mismo software instalado.

    Investigando un poco y, hay que decirlo también, por consejo de algunos colegas me metí con docker.

    Qué es Docker

    Docker es una herramienta de virtualización basada en un concepto algo diferente al que usa Vagrant: los contenedores.

    No me voy a meter acá en los detalles técnicos, simplemente diré que un contenedor hace un uso mucho más eficiente de los recursos del hardware y, a los fines prácticos, cumple la misma función que una VM.

    Cómo se usa Docker

    Docker se basa en el docker daemon corriendo constantemente y luego en un cliente que le envía comandos.

    Lo primero que necesitás es tener algún contenedor levantado… y para eso, lo primero que necesitás es tener algún contenedor creado.

    Los contenedores se crean en base a imágenes.

    Qué es una imagen de Docker

    Una imagen de docker es una definición de un entorno de ejecución completo (algo así como una VM de Vagrant).

    Un contenedor es una imagen en ejecución.

    Podrías pensarlo como la diferencia entre un programa y un proceso.

    Las imágenes docker se definen en un archivo de texto llamado Dockerfile (Bastante similar al Vagrantfile).

    Por ejemplo:

    FROM php:7.2-cli
    COPY . /usr/src/myapp
    WORKDIR /usr/src/myapp
    CMD [ "php", "./your-script.php" ]

    Lo que ves es una serie de instrucciones.

    La primera (FROM) es tal vez la más importante: la imagen base.

    Las imágenes docker manejan un concepto similar al de la herencia de POO: Sobre una imagen base es posible crear otras más especializadas.

    Para efectivamente arrancar esta imagen debemos crear un contenedor basado en ella.

    Antes de hacer eso es conveniente crear un archivo llamado your-script.php (Es importante hacerlo antes de crear el contenedor porque el comando COPY se ejecutará durante la creación y no volverá a ejecutarse hasta que el contenedor sea destruido y vuelto a crear).

    En cierto sentido, esto es como quemar una ROM 🙂

    Pues entonces, creemos un archivo your-script.php con este contenido:

    <?php
    
    echo "Estoy en docker!!".PHP_EOL;

    Y ahora sí ¡estamos listos para darle vida a nuestro primer contenedor docker!

    sudo docker build -t my-php-app .

    Con este comando hemos creado nuestro contenedor.

    Para ejecutar algún comando dentro de él usaremos:

    sudo docker run -it --rm --name my-running-app my-php-app

    Y obtendremos la salida:

    Estoy en docker!!

    Cómo correr una aplicación web PHP en docker

    Típicamente para ejecutar una aplicación web PHP vas a necesitar al menos un servidor web y el intérprete de PHP instalado.

    Podrías definir tu propia imagen instalando todos los paquetes y demás, pero… ¿vale realmente la pena?

    No.

    Puedes basar tu imagen en una que ya tenga un poco más allanado el camino.

    Por ejemplo php:7.3-apache está basada en Ubutu 18.04 y ya trae php 7.3, Apache y algún par de utilidades más.

    De modo que usando un Dockerfile como este:

    FROM php:7.3-apache
    COPY . /var/www/html/

    Podremos tener un contenedor que incluya un Apache y nuestro código en el DocumentRoot.

    Un detalle importante que falta es «avisar» a docker que queremos que nuestro contenedor pueda ser accedido desde afuera a través del puerto 80 (Muy parecido al mapeo de puertos que se usa en Vagrant).

    Para eso vamos a usar el comando EXPOSE. El Dockerfile se verá así:

    FROM php:7.3-apache
    COPY . /var/www/html/
    EXPOSE 80

    Para no complicar mucho las cosas renombremos el archivo your-script.php a index.php y usemos este comando para crear nuestro contenedor:

    sudo docker build -t my-php-web-app .

    Para entrar a ver nuestro flamante sitio tenemos que ejecutar:

    sudo docker run -p 80:80 --rm -it --name my-web-app my-php-web-app:latest

    Y, por supuesto, abrir un navegador en http://localhost.

    Si todo salió bien deberías ver algo como:

    Cómo desplegar una imagen Docker en Producción

    Muy bien, ya tenemos todo casi listo, sólo nos falta ver cómo desplegar nuestra imagen en un servidor de producción y concluimos.

    Claramente, esta es la parte que realmente hace una diferencia respecto de usar Vagrant… difícilmente vas a querer montar una VM en un servidor productivo por cada aplicación que tengas… o vas a tener una factura bien abultada en AWS.

    Existen varias opciones para realizar el despliegue. Para mantener este artículo dentro de un alcance acotado me voy a limitar a la que considero requiere menos conocimientos extra:

    1. Grabar la imagen en nuestra máquina local
    sudo docker save -o image.zip my-php-web-app
    1. Subir la imagen al servidor de producción
    scp image.zip <user>@<server-addres>:<target-location>
    1. Agregar la imagen al repositorio de docker
    docker load -i <path-to-image.zip>
    1. Crear el contenedor a partir de la imagen
    docker run -d -p 8888:80 my-php-web-app

    Y listo! Ahora se puede acceder a la aplicación entrando a la dirección pública del servidor y el puerto 8888 (Y si es tu única webapp podrías directamente mapear el puerto 80 de tu servidor al del contenedor).

  • Docker o VM ¿Cuál es mejor para PHP?

    Estás montando tu entorno de trabajo local para trabajar en un proyecto PHP.

    Hay muchas cosas por definir pero una es segura: no vas a usar XAMPP ni nada parecido… después del último fiasco fue suficiente, hay que virtualizar sí o sí.

    Y la pregunta es: ¿voy con Docker o con una VM?

    Creeme que te entiendo, estuve mucho tiempo en tus zapatos.

    Hoy por hoy mi elección es clara: Docker. Pero no te pido que me creas así sin más, dejame darte una breve explicación para que saques tu conclusión.

    En qué se parecen y en qué se diferencian Docker y las Máquinas Virtuales

    Docker es un sistema de administración de contenedores. Sin entrar en mucho detalle, diré que es un método para crear y administrar entornos de ejecución aislados del resto.

    Las máquinas virtuales son sistemas de simulación de hardware.

    ¿Entonces…?

    Las máquinas virtuales son, en cierta medida, más poderosas ya que permiten crear un entorno de computación «completamente» separado del host, donde puede instalarse, por ejemplo, un sistema operativo diferente de aquel del host.

    Docker (o algún otro sistema de contenedores) en cambio no virtualiza hardware, si no software. Esto significa que sus posibilidades son algo más limitadas (Por ejemplo, montar un contenedor Linux en un host Windows es algo que, en principio no es posible).

    Hasta aquí la balanza parece inclinarse hacia las VMs, ¿no? Entonces… ¿por qué deberías preferir Docker?

    Los contenedores Docker son mucho más livianos que las VMs. Esto implica que:

    • Se inician/frenan mucho más rápido
    • Ocupan mucho menos espacio en el disco
    • Consumen muchos menos recursos del host

    Todo esto hace que sea mucho más viable tener un gran número de contenedores Docker ejecutando en un mismo host, a diferencia de lo que ocurre con las máquinas virtuales.

    Dejando estos detalles de implementación de lado, en la práctica las diferencias son mínimas. Cambia un poco la forma de configurar el entorno pero ambos se prestan muy bien para la automatización.

    Tanto Docker como los sistemas de virtualización cuentan con interfaces de línea de comandos muy potentes y, sobre esas herramientas básicas se montan algunas más avanzadas (Vagrant para el caso de VMs y docker-compose para Docker) o incluso algunas con interface gráfica, lo cual facilita mucho su uso.

    De modo que, siendo que el costo de implementar una o la otra es prácticamente igual, ¿por qué ir con la que más recursos consume?

    Por supuesto que la realidad es más compleja, hay que analizar el caso por caso pero, en líneas generales, mi recomendación es ir con Docker.

    Cómo arrancar con Docker

    Bueno, este tema excede un poco el objetivo de este post, pero voy a intentar hacer un breve resúmen y dejarte algunas referencias que puedan ayudarte:

    1. Instalar Docker
    2. Buscar una imagen que puedas usar como prueba
    3. Crear un contenedor
    4. Correr el «Hola Mundo!» 🙂

    Esto es una visión muy reducida. Si querés más detalles te invito a leer este post.

    Y si ya diste tus primeros pasos pero tenés dudas, te recomiendo mirar la Guía Práctica de Dockerización de PHP.

  • Qué es un CDN y por qué deberías usarlo

    Qué es un CDN y por qué deberías usarlo

    CDN significa Content Delivery Network o Red de Distribución de Contenidos.

    Se trata de conjuntos (por lo general bastante grandes) de servidores sincronizados entre sí y preparados para servir contenido estático desde diversos puntos del planeta.

    Su objetivo principal es el de disminuir el tiempo de carga de una página web (Algo que siempre viene bien).

    Este objetivo se logra combinando varios factores. Entre ellos:

    1. Aprovechando el paralelismo de los pedidos HTTP: Al tener el contenido distribuido en diversos servidores (en lugar de tener el código php y los archivos estáticos en el mismo) el cliente puede lanzar varias peticiones en paralelo por un lado y, por el otro, el pobre servidor al que llegan todos los visitantes de tu sitio puede delegar parte de la carga, lo cual a su vez hace todo el proceso más ligero.
    2. Sirviendo el contenido estático desde la locación física más cercana a cada cliente. Parte de la magia del CDN es tener la capacidad de identificar velozmente cuál es el nodo que responderá con menos latencia (Generalmente el que se encuentre más próximo al cliente).

    Qué se puede almacenar en un CDN

    Cualquier recurso estático (html, javascript, imágenes, etc…) es susceptible de ser almacenado en un CDN. De hecho, la mayoría de las librerías populares de javascript (Como jQuery por ejemplo) tienen sus propios CDNs que podés usar para cualquier proyecto, basta con hacer un include como este:

    <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    </head>

    De esta forma, en lugar de usar un archivo de jQuery alojado en tu propio servidor, usás la capacidad instalada de Google.

    Otra ventaja de usar un CDN (En realidad esto no es particular del CDN si no de cualquier fuente conocida) es que muy probablemente el cliente ya haya descargado el archivo que tu sitio necesita para funcionar, aún cuando se trata de la primera visita (Otra optimización de performance que realizan los navegadores).

    Alternativas

    CloudFlareExiste una gran cantidad de empresas que ofrecen servicios de CDN, te dejo acá algunas que yo he usado en proyectos propios:

    En cuanto a su funcionalidad están todas bastante cerca, el factor que suele inclinar la balanza es el precio… ahí CloudFlare es un claro ganador ya que ofrece un muy buen paquete gratuito.

    ¿Qué opinás? ¿Vas a implementar un CDN en tu próximo proyecto?

  • Qué es y para qué sirve PHP Mess Detector

    Qué es y para qué sirve PHP Mess Detector

    PHPMessDetector es una herramienta que ayuda a detectar código defectuoso en proyectos PHP (Muy útil a la hora de realizar auditorías de código ajeno).

    Se basa en el análisis automatizado del código utilizando conjuntos de reglas.

    Estas reglas buscan detectar código mal estructurado, mala nomenclatura, métodos exageradamente grandes, ciclos ineficientes y demás.

    Está preparado para emitir su salida en diferentes formatos (Texto, XML, HTML).

    Su uso es bastante simple, hay que instalarlo y luego corre como una utilidad de línea de comandos.

    Por ejemplo, corriendo el comando

    php phpmd.phar . text naming

    Sobre una base de código algo dudoso, obtuve un resultado como este:

    /home/mauro/sitio/authorize/authorizenet.php:11 Classes should not have a constructor method with the same name as the class/home/mauro/sitio/authorize/authorizenet.php:11 Classes should not have a constructor method with the same name as the class/home/mauro/sitio/authorize/authorizenet.php:42 Avoid variables with short names like $ch. Configured minimum length is 3./home/mauro/sitio/authorize/authorizenet.php:87 Avoid variables with short names like $j. Configured minimum length is 3.

    Otro análisis interesante es este:

    php phpmd.phar . text codesize

    Que me da este resultado (Entre otros):

    /home/mauro/sitio/phpminiadmin.php:157	The function display_select() has an NPath complexity of 26112. The configured NPath complexity threshold is 200.

    Si vemos lo que hay a partir de la línea 157:

    function display_select($sth,$q){
     global $dbh,$DB,$sqldr,$reccount,$is_sht,$xurl;
     $rc=array("o","e");
     $dbn=$DB['db'];
     $sqldr='';
    
     $is_shd=(preg_match('/^show\s+databases/i',$q));
     $is_sht=(preg_match('/^show\s+tables|^SHOW\s+TABLE\s+STATUS/',$q));
     $is_show_crt=(preg_match('/^show\s+create\s+table/i',$q));
    
     if ($sth===FALSE or $sth===TRUE) return;#check if $sth is not a mysql resource
    
     $reccount=mysql_num_rows($sth);
     $fields_num=mysql_num_fields($sth);
    
     $w='';
     if ($is_sht || $is_shd) {$w='wa';
       $url='?'.$xurl."&db=$dbn";
       $sqldr.="<div class='dot'>
    &nbsp;MySQL Server:
    &nbsp;&#183;<a href='$url&q=show+variables'>Show Configuration Variables</a>
    &nbsp;&#183;<a href='$url&q=show+status'>Show Statistics</a>
    &nbsp;&#183;<a href='$url&q=show+processlist'>Show Processlist</a>
    <br>";
       if ($is_sht) $sqldr.="&nbsp;Database:&nbsp;&#183;<a href='$url&q=show+table+status'>Show Table Status</a>";
       $sqldr.="</div>";
     }
     if ($is_sht){
       $abtn="&nbsp;<input type='submit' value='Export' onclick=\"sht('exp')\">
     <input type='submit' value='Drop' onclick=\"if(ays()){sht('drop')}else{return false}\">
     <input type='submit' value='Truncate' onclick=\"if(ays()){sht('trunc')}else{return false}\">
     <input type='submit' value='Optimize' onclick=\"sht('opt')\">
     <b>selected tables</b>";
       $sqldr.=$abtn."<input type='hidden' name='dosht' value=''>";
     }
    
     $sqldr.="<table class='res $w'>";
     $headers="<tr class='h'>";
     if ($is_sht) $headers.="<td><input type='checkbox' name='cball' value='' onclick='chkall(this)'></td>";
     for($i=0;$i<$fields_num;$i++){
        if ($is_sht && $i>0) break;
        $meta=mysql_fetch_field($sth,$i);
        $headers.="<th>".$meta->name."</th>";
     }
     if ($is_shd) $headers.="<th>show create database</th><th>show table status</th><th>show triggers</th>";
     if ($is_sht) $headers.="<th>engine</th><th>~rows</th><th>data size</th><th>index size</th><th>show create table</th><th>explain</th><th>indexes</th><th>export</th><th>drop</th><th>truncate</th><th>optimize</th><th>repair</th>";
     $headers.="</tr>\n";
     $sqldr.=$headers;
     $swapper=false;
     while($row=mysql_fetch_row($sth)){
       $sqldr.="<tr class='".$rc[$swp=!$swp]."' onmouseover='tmv(this)' onmouseout='tmo(this)' onclick='tc(this)'>";
       for($i=0;$i<$fields_num;$i++){
          $v=$row[$i];$more='';
          if ($is_sht && $v){
             if ($i>0) break;
             $vq='`'.$v.'`';
             $url='?'.$xurl."&db=$dbn";
             $v="<input type='checkbox' name='cb[]' value=\"$vq\"></td>"
             ."<td><a href=\"$url&q=select+*+from+$vq\">$v</a></td>"
             ."<td>".$row[1]."</td>"
             ."<td align='right'>".$row[4]."</td>"
             ."<td align='right'>".$row[6]."</td>"
             ."<td align='right'>".$row[8]."</td>"
             ."<td>&#183;<a href=\"$url&q=show+create+table+$vq\">sct</a></td>"
             ."<td>&#183;<a href=\"$url&q=explain+$vq\">exp</a></td>"
             ."<td>&#183;<a href=\"$url&q=show+index+from+$vq\">ind</a></td>"
             ."<td>&#183;<a href=\"$url&shex=1&t=$vq\">export</a></td>"
             ."<td>&#183;<a href=\"$url&q=drop+table+$vq\" onclick='return ays()'>dr</a></td>"
             ."<td>&#183;<a href=\"$url&q=truncate+table+$vq\" onclick='return ays()'>tr</a></td>"
             ."<td>&#183;<a href=\"$url&q=optimize+table+$vq\" onclick='return ays()'>opt</a></td>"
             ."<td>&#183;<a href=\"$url&q=repair+table+$vq\" onclick='return ays()'>rpr</a>";
          }elseif ($is_shd && $i==0 && $v){
             $url='?'.$xurl."&db=$v";
             $v="<a href=\"$url&q=SHOW+TABLE+STATUS\">$v</a></td>"
             ."<td><a href=\"$url&q=show+create+database+`$v`\">sct</a></td>"
             ."<td><a href=\"$url&q=show+table+status\">status</a></td>"
             ."<td><a href=\"$url&q=show+triggers\">trig</a></td>"
             ;
          }else{
           if (is_null($v)) $v="NULL";
           $v=htmlspecialchars($v);
          }
          if ($is_show_crt) $v="<pre>$v</pre>";
          $sqldr.="<td>$v".(!strlen($v)?"<br>":'')."</td>";
       }
       $sqldr.="</tr>\n";
     }
     $sqldr.="</table>\n".$abtn;
    
    }

    Podemos ver un código algo complicado… aún si no sabemos calcular el NPathComplexity :).

    Si bien las reglas que trae incorporada la herramienta suelen dar buenos indicios, no debe tomarse todo como verdad incuestionable.

    Particularmente, hay una regla que, al menos a mi, me genera cierta desconfianza: ElseExpression (Del conjunto CleanCode).

    Según su definición, una sentencia else nunca es necesaria y sólo hace al código más difícil de leer… probablemente tenga razón en la primera parte (De que no es 100% necesaria, ahora, si hace más difícil de leer el código… no me parece tan simple de afirmar).

    De cualquier forma, es interesante contar con una herramienta que pueda orientar nuestros esfuerzos rápidamente hacia las partes más controvertidas del código.

    Una característica muy interesante de PHPMD es que te permite definir tus propias reglas de análisis (Yo por ejemplo usaría una que detecte el uso de variables globales).

    Si bien es evidente que sólo con el análisis estático del código no es posible dar un diagnóstico completo, ciertamente ayuda contar con este tipo de herramientas.

    ¿Alguna vez te tocó analizar código de terceros? ¿Cómo te las arreglaste?

  • Cuál es el mejor IDE para PHP

    Cuál es el mejor IDE para PHP

    Si llevás algo de tiempo trabajando con PHP, te habrás dado cuenta de que ni notepad ni vim ni nada que se le parezca es suficientemente bueno como para que desarrollar aplicaciones sea una tarea amena.

    Para alcanzar un buen nivel de eficiencia se necesitan herramientas específicas que nos simplifiquen (un poco) la vida.

    En el caso de la programación, lo que más va a modificar tu día a día es el uso (o falta) de un Entorno Integrado de Desarrollo (IDE en Inglés).

    Existen muchas opciones cuando se trata de PHP, sin embargo, hay unos pocos que son los estándares de la industria. Veamos algunos:

    Eclipse para PHP

    En realidad no se trata de un producto específico, si no de un plugin para el ya conocido Eclipse (El IDE multiplataforma).

    Entre sus ventajas se cuentan el hecho de que está muy extendido su uso, hay muchos plugins y es (o era al menos) la opción de facto (Especialmente si trabajás con otros lenguajes como Java, seguramente alguna vez lo usaste).

    Ah, y es gratis :).

    Su principal desventaja es que consume muchísimos recursos y es sumamente lento y pesado.

    Mucho de esto se debe al hecho de que, al intentar ser una solución a muchos problemas, tiene muchas características que no son estrictamente necesarias…

    Lo podés descargar de acá.

    ZendStudio

    Escencialmente se trata de un Eclipse lookeado. Zend Technologies es una empresa que está muy metida en el desarrollo de PHP. Si ejecutás php -v en una terminal te vas a encontrar con algo como:

    PHP 7.2.4-1+ubuntu14.04.1+deb.sury.org+1 (cli) (built: Apr  5 2018 11:08:49) ( NTS )
    Copyright (c) 1997-2018 The PHP Group
    Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
        with Zend OPcache v7.2.0-1+ubuntu14.04.1+deb.sury.org+1, Copyright (c) 1999-2017, by Zend Technologies
        with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans

    Lo cual daría a pensar que sus productos son superiores a los de la competencia… sin embargo, en mi experiencia al menos, no ha sido así (De hecho, casi diría que lo contrario es cierto).

    Además de este IDE, existe el ZendFramework y un entorno de debug propio de Zend (Yo personalmente prefiero Symfony y XDebug).

    Detalle importante: no es gratis, pero tiene un trial que podés descargar de acá.

    NetBeans

    Esta opción desarrollada por Sun Microsystems (Ahora adquirida por Oracle) es realmente interesante. Tiene una base similar a la de Eclipse (Es un IDE pensado como un genérico al que se le puede instalar un plugin para usarlo con PHP), pero logra una performance altamente superior (Lo que le da una mucho mejor experiencia de usuario).

    Se integra muy bien con otras herramientas típicas del desarrollo con PHP (Controladores de versiones, Vagrant, etc…).

    Entre las opciones gratuitas es mi preferida (De hecho, era mi entorno favorito hasta que encontré PhpStorm), la podés descargar de acá.

    PhpStorm

    Abran paso que llegó el rey: PhpStorm.

    Este IDE es por lejos lo mejor que yo he probado. Tiene una cantidad enorme de características y montones de opciones de customización.

    Es MUY rápido y, al ser tan específico de PHP, no le sobra nada 🙂 y me arriesgaría a decir que tiene cosas que los otros no.

    Integra muy bien con cualquier framework conocido y permite, si se configura bien, no salir del IDE para ninguna tarea de desarrollo (Se puede correr UnitTests, manejar dependencias con composer, hacer debugging remoto, etc…).

    Para ser justo con los demás, debo aclarar que hace mucho tiempo que es mi entorno de trabajo, con lo cual, puede que los otros hayan evolucionado y hoy le hagan fuerza…

    Pero tiene un problemita: es un software pago (y no precisamente barato.). Si podés pagar la licencia (o lograr que tu empresa te lo pague), realmente lo vale.

    Podés descargar una versión de prueba acá.

    VisualStudioCode

    El nuevo jugador que está tomando fuerza es VisualStudioCode. Lo probé muy poco y no logró convencerme de hacer el cambio… lo nombro simplemente porque muchos otros desarrolladores lo usan y están muy contentos con él.

    A mi mucho los productos de MicroSoft no me gustan, pero… este parece estar bien logrado.

    Descargalo de acá y me contás :).

    CodeAnyWhere

    Un proyecto espectacular, pero aún bastante verde para mi gusto… La idea es bárbara: un IDE 100% basado en web.

    Si tus requerimientos te lo permiten, es la mejor opción para independizarte por completo de tu computadora (Algo que para cualquier nómada digital debería ser un factor de peso).

    Por lo poco que lo probé, no me pudo convencer de cambiar, pero no me sorprendería que en un par de años se convierta en mi aliado favorito.

    No hay nada que descargar, sólo tenés que crear una cuenta acá y salir codeando :).

    Codelobster

    Un proyecto con el que me crucé recientemente y que vale la pena mencionar es Codelobster.

    Se trata de un IDE multiplataforma y que tiene una versión gratuita.

    Sus puntos fuertes son:

    • La inclusión de soporte para los frameworks más populares de PHP
    • La velocidad
    • El bajo consumo de recursos
    • La sencillez de su interface

    Una opción para tener en cuenta.

    Conclusión

    IDEs hay de todos los tamaños y colores… en última instancia, es una cuestión de gusto/comodidad personal. Mi sugerencia es que pruebes varios, elijas uno y aprendas a sacarle hasta la última gota de jugo.

    ¿Qué herramientas forman parte imprescindible de tu toolkit?

  • Cómo probar tus scripts PHP sin instalar un intérprete

    Cómo probar tus scripts PHP sin instalar un intérprete

    No sé si te pasará lo mismo, pero para mi, la posibilidad de escribir (y probar) código sin necesidad de tener mi propia computadora a mano es muy seductora.

    He probado CodeAnyWhere y está bastante cerca de lo que me gustaría, pero todavía (2017) le falta un poco… imagino que para 2020 será la opción de facto… por el momento hay que conformarse con opciones un poco menos sofisticadas (o llevar la compu a cuestas… lo que prefieras).

    Pero bueno, cuando se trata de tirar algún comando rápido, hacer alguna prueba de concepto o simplemente matar unos minutos libres, acá te dejo algunas opciones interesantes:

    Run Write PHP Online

    Una interfase muy bonita, bien pulida y, lo mejor, interpreta mientras escribís y te da la salida (o el error) inmediatamente.

    (Me comenta mi amigo @MySelfCoding que este no va más… una pena :'( )

    WritePHPOnline

    Un poco menos profesionale que el anterrior, la interfase no es tan limpia, pero bueno… cumple con su cometido.

    Tiene un lindo cheatsheet de funciones php que puede venirte bien.

    PHPFiddle

    Esta es una opción que no conocía, por lo que se ve, parece querer ser un competidor de CodeAnywhere… aunque está bastante lejos de lograrlo… para investigarlo un poco.

    PHPTester

    También, una herramienta algo rústica, pero que cumple.

    Permite elegir la versión del intérprete.

    RunPHPOnline

    Muy similar a PHPTester, con la UI algo más pulida.

    ¿Qué otras opciones conocés para testear código PHP rápidamente?

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

  • ¿Qué es PHP Composer? Todo lo que Necesitas Saber

    ¿Qué es PHP Composer? Todo lo que Necesitas Saber

    Estás desarrollando una aplicación PHP y te enfrentás a un problema que seguro alguien ya resolvió antes.

    Para qué re-inventar la rueda, ¿cierto?

    Abrís una nueva pestaña, entrás a Google y escribís «Cómo hacer X con php».

    La lista es larga, pero hay un factor común en todos los ejemplos: composer.

    Ahí está otra vez.

    Composer por aquí, composer por allá.

    Por todos lados te muestran cosas como:

    php composer require base/demo-base-code

    Como quien dice «Buenos días».

    Y, a pesar de no ser precisamente un newbie, te frustra tener que copiar y pegar… y rezar.

    Necesitás entender qué es composer de una buena vez.

    Así que… ¿de qué se trata el famoso php composer?

    Qué es Composer

    Composer es un gestor de dependencias para PHP (Similar a lo que npm es para JavaScript o pip para Python).

    Es una aplicación PHP que ayuda a administrar las librerías desarrolladas por terceros que vas a incorporar a tu proyecto.

    De algún modo podrías considerarlo como el heredero de PEAR.

    Hoy día se considera el gestor de dependencias de-facto por unas cuantas buenas razones:

    • Es muy simple de operar
    • Cuenta con un repositorio super completo (Packagist)
    • Disminuye significativamente los problemas de cambio de ambiente de ejecución (Mediante su funcionalidad de congelar dependencias)

    Si nunca lo usaste, te recomiendo que lo pruebes.

    Cómo se instala Composer

    La instalación de composer es sumamente simple. Como dice el sitio getcomposer.org en su página de descargas:

    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
    php composer-setup.php
    php -r "unlink('composer-setup.php');"

    A partir de ahí tendrás disponible un comando llamado composer (Qué sorpresa ¿no?). Si lo ejecutas sin decir más nada te va a dar una lista de los sub-comandos disponibles:

      about           Shows the short information about Composer.
      archive         Creates an archive of this composer package.
      browse          Opens the package's repository URL or homepage in your browser.
      clear-cache     Clears composer's internal package cache.
      clearcache      Clears composer's internal package cache.
      config          Sets config options.
      create-project  Creates new project from a package into given directory.
      depends         Shows which packages cause the given package to be installed.
      diagnose        Diagnoses the system to identify common errors.
      dump-autoload   Dumps the autoloader.
      dumpautoload    Dumps the autoloader.
      exec            Executes a vendored binary/script.
      global          Allows running commands in the global composer dir ($COMPOSER_HOME).
      help            Displays help for a command
      home            Opens the package's repository URL or homepage in your browser.
      info            Shows information about packages.
      init            Creates a basic composer.json file in current directory.
      install         Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
      licenses        Shows information about licenses of dependencies.
      list            Lists commands
      outdated        Shows a list of installed packages that have updates available, including their latest version.
      prohibits       Shows which packages prevent the given package from being installed.
      remove          Removes a package from the require or require-dev.
      require         Adds required packages to your composer.json and installs them.
      run-script      Runs the scripts defined in composer.json.
      search          Searches for packages.
      self-update     Updates composer.phar to the latest version.
      selfupdate      Updates composer.phar to the latest version.
      show            Shows information about packages.
      status          Shows a list of locally modified packages.
      suggests        Shows package suggestions.
      update          Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file.
      validate        Validates a composer.json and composer.lock.
      why             Shows which packages cause the given package to be installed.
      why-not         Shows which packages prevent the given package from being installed.

    Te voy a mostrar algunas pocas cosas como para que veas el poder que tiene:

    Cómo funciona Composer

    Composer trabaja con dos archivos de configuración: composer.json y composer.lock.

    El segundo es más bien de uso interno así que no hace falta conocer muchos detalles sobre él.

    El primero en cambio es el que vas a usar bastante a menudo ya que en él se declaran todas las dependencias que tu sistema tendrá.

    Es muy simple de leer (al igual que cualquier json) y permite una gran flexibilidad para definir las dependencias (por ejemplo se puede decir a partir de qué versión se acepta) tanto de librerías escritas en php como del intérprete y sus extensiones.

    Típicamente un archivo composer.json se ve más o menos así:

    {
      "name": "leeway/scrapper",
      "description": "Un pequeño web scrapper",
      "minimum-stability": "stable",
      "license": "proprietary",
      "authors": [
        {
          "name": "Mauro Chojrin",
          "email": "mauro.chojrin@leewayweb.com"
        }
      ],
      "require": {
        "fabpot/goutte": "v3.2.0"
      }
    }

    Donde la parte más importante es lo que está dentro de require (Las dependencias específicas del proyecto).

    Se puede crear el archivo «manualmente» o, mejor, se puede usar el comando composer init que lo hace por nosotros (y aparte permite definir algunas propiedades en forma interactiva).

    Pero claramente, de poco sirve la declaración de las dependencias si no tenemos las librerías instaladas…

    Cómo instalar librerías usando Composer

    Para instalar librerías composer dispone del comando install, el cual se encarga de bajarlas, armar el autoloading y demás… ¡magia!.

    Lo único que te queda por hacer es require_once 'vendor/autoload.php'; donde vayas a usar las librerías instaladas y voilà.

    Hasta acá todo muy lindo, se pueden instalar y usar dependencias con muy poco esfuerzo pero… ¿Te pasó alguna vez que desarrollaste un sistema, en tu computadora andaba 10 puntos pero al subirlo a producción daba algún error raro?

    ¿Te pasó estar horas dando vueltas sin entender nada hasta que te diste cuenta de que la versión 3.4.1 de la librería de envío de mails no era compatible con la 3.4.0 (Instalada en producción)?

    Justamente para eso composer tiene el archivo composer.lock. Si bien es un archivo de texto, ¡no lo toques!.

    Te muestro el que corresponde al composer.json que veías:

    {
        "_readme": [
            "This file locks the dependencies of your project to a known state",
            "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
            "This file is @generated automatically"
        ],
        "content-hash": "cc70bd9c59b4fdbb7e8e632402e15bcc",
        "packages": [
            {
                "name": "fabpot/goutte",
                "version": "v3.2.0",
                "source": {
                    "type": "git",
                    "url": "https://github.com/FriendsOfPHP/Goutte.git",
                    "reference": "8cc89de5e71daf84051859616891d3320d88a9e8"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/8cc89de5e71daf84051859616891d3320d88a9e8",
                    "reference": "8cc89de5e71daf84051859616891d3320d88a9e8",
                    "shasum": ""
                },
                "require": {
                    "guzzlehttp/guzzle": "^6.0",
                    "php": ">=5.5.0",
                    "symfony/browser-kit": "~2.1|~3.0",
                    "symfony/css-selector": "~2.1|~3.0",
                    "symfony/dom-crawler": "~2.1|~3.0"
                },
                "type": "application",
                "extra": {
                    "branch-alias": {
                        "dev-master": "3.2-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Goutte\\": "Goutte"
                    }
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Fabien Potencier",
                        "email": "fabien@symfony.com"
                    }
                ],
                "description": "A simple PHP Web Scraper",
                "homepage": "https://github.com/FriendsOfPHP/Goutte",
                "keywords": [
                    "scraper"
                ],
                "time": "2016-11-15T16:27:29+00:00"
            },
            {
                "name": "guzzlehttp/guzzle",
                "version": "6.3.0",
                "source": {
                    "type": "git",
                    "url": "https://github.com/guzzle/guzzle.git",
                    "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
                    "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
                    "shasum": ""
                },
                "require": {
                    "guzzlehttp/promises": "^1.0",
                    "guzzlehttp/psr7": "^1.4",
                    "php": ">=5.5"
                },
                "require-dev": {
                    "ext-curl": "*",
                    "phpunit/phpunit": "^4.0 || ^5.0",
                    "psr/log": "^1.0"
                },
                "suggest": {
                    "psr/log": "Required for using the Log middleware"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "6.2-dev"
                    }
                },
                "autoload": {
                    "files": [
                        "src/functions_include.php"
                    ],
                    "psr-4": {
                        "GuzzleHttp\\": "src/"
                    }
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Michael Dowling",
                        "email": "mtdowling@gmail.com",
                        "homepage": "https://github.com/mtdowling"
                    }
                ],
                "description": "Guzzle is a PHP HTTP client library",
                "homepage": "http://guzzlephp.org/",
                "keywords": [
                    "client",
                    "curl",
                    "framework",
                    "http",
                    "http client",
                    "rest",
                    "web service"
                ],
                "time": "2017-06-22T18:50:49+00:00"
            },
            {
                "name": "guzzlehttp/promises",
                "version": "v1.3.1",
                "source": {
                    "type": "git",
                    "url": "https://github.com/guzzle/promises.git",
                    "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
                    "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
                    "shasum": ""
                },
                "require": {
                    "php": ">=5.5.0"
                },
                "require-dev": {
                    "phpunit/phpunit": "^4.0"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "1.4-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "GuzzleHttp\\Promise\\": "src/"
                    },
                    "files": [
                        "src/functions_include.php"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Michael Dowling",
                        "email": "mtdowling@gmail.com",
                        "homepage": "https://github.com/mtdowling"
                    }
                ],
                "description": "Guzzle promises library",
                "keywords": [
                    "promise"
                ],
                "time": "2016-12-20T10:07:11+00:00"
            },
            {
                "name": "guzzlehttp/psr7",
                "version": "1.4.2",
                "source": {
                    "type": "git",
                    "url": "https://github.com/guzzle/psr7.git",
                    "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
                    "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
                    "shasum": ""
                },
                "require": {
                    "php": ">=5.4.0",
                    "psr/http-message": "~1.0"
                },
                "provide": {
                    "psr/http-message-implementation": "1.0"
                },
                "require-dev": {
                    "phpunit/phpunit": "~4.0"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "1.4-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "GuzzleHttp\\Psr7\\": "src/"
                    },
                    "files": [
                        "src/functions_include.php"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Michael Dowling",
                        "email": "mtdowling@gmail.com",
                        "homepage": "https://github.com/mtdowling"
                    },
                    {
                        "name": "Tobias Schultze",
                        "homepage": "https://github.com/Tobion"
                    }
                ],
                "description": "PSR-7 message implementation that also provides common utility methods",
                "keywords": [
                    "http",
                    "message",
                    "request",
                    "response",
                    "stream",
                    "uri",
                    "url"
                ],
                "time": "2017-03-20T17:10:46+00:00"
            },
            {
                "name": "psr/http-message",
                "version": "1.0.1",
                "source": {
                    "type": "git",
                    "url": "https://github.com/php-fig/http-message.git",
                    "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
                    "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
                    "shasum": ""
                },
                "require": {
                    "php": ">=5.3.0"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "1.0.x-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Psr\\Http\\Message\\": "src/"
                    }
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "PHP-FIG",
                        "homepage": "http://www.php-fig.org/"
                    }
                ],
                "description": "Common interface for HTTP messages",
                "homepage": "https://github.com/php-fig/http-message",
                "keywords": [
                    "http",
                    "http-message",
                    "psr",
                    "psr-7",
                    "request",
                    "response"
                ],
                "time": "2016-08-06T14:39:51+00:00"
            },
            {
                "name": "symfony/browser-kit",
                "version": "v3.3.10",
                "source": {
                    "type": "git",
                    "url": "https://github.com/symfony/browser-kit.git",
                    "reference": "317d5bdf0127f06db7ea294186132b4f5b036839"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/symfony/browser-kit/zipball/317d5bdf0127f06db7ea294186132b4f5b036839",
                    "reference": "317d5bdf0127f06db7ea294186132b4f5b036839",
                    "shasum": ""
                },
                "require": {
                    "php": "^5.5.9|>=7.0.8",
                    "symfony/dom-crawler": "~2.8|~3.0"
                },
                "require-dev": {
                    "symfony/css-selector": "~2.8|~3.0",
                    "symfony/process": "~2.8|~3.0"
                },
                "suggest": {
                    "symfony/process": ""
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "3.3-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Symfony\\Component\\BrowserKit\\": ""
                    },
                    "exclude-from-classmap": [
                        "/Tests/"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Fabien Potencier",
                        "email": "fabien@symfony.com"
                    },
                    {
                        "name": "Symfony Community",
                        "homepage": "https://symfony.com/contributors"
                    }
                ],
                "description": "Symfony BrowserKit Component",
                "homepage": "https://symfony.com",
                "time": "2017-10-02T06:42:24+00:00"
            },
            {
                "name": "symfony/css-selector",
                "version": "v3.3.10",
                "source": {
                    "type": "git",
                    "url": "https://github.com/symfony/css-selector.git",
                    "reference": "07447650225ca9223bd5c97180fe7c8267f7d332"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/symfony/css-selector/zipball/07447650225ca9223bd5c97180fe7c8267f7d332",
                    "reference": "07447650225ca9223bd5c97180fe7c8267f7d332",
                    "shasum": ""
                },
                "require": {
                    "php": "^5.5.9|>=7.0.8"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "3.3-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Symfony\\Component\\CssSelector\\": ""
                    },
                    "exclude-from-classmap": [
                        "/Tests/"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Jean-François Simon",
                        "email": "jeanfrancois.simon@sensiolabs.com"
                    },
                    {
                        "name": "Fabien Potencier",
                        "email": "fabien@symfony.com"
                    },
                    {
                        "name": "Symfony Community",
                        "homepage": "https://symfony.com/contributors"
                    }
                ],
                "description": "Symfony CssSelector Component",
                "homepage": "https://symfony.com",
                "time": "2017-10-02T06:42:24+00:00"
            },
            {
                "name": "symfony/dom-crawler",
                "version": "v3.3.10",
                "source": {
                    "type": "git",
                    "url": "https://github.com/symfony/dom-crawler.git",
                    "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/40dafd42d5dad7fe5ad4e958413d92a207522ac1",
                    "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1",
                    "shasum": ""
                },
                "require": {
                    "php": "^5.5.9|>=7.0.8",
                    "symfony/polyfill-mbstring": "~1.0"
                },
                "require-dev": {
                    "symfony/css-selector": "~2.8|~3.0"
                },
                "suggest": {
                    "symfony/css-selector": ""
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "3.3-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Symfony\\Component\\DomCrawler\\": ""
                    },
                    "exclude-from-classmap": [
                        "/Tests/"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Fabien Potencier",
                        "email": "fabien@symfony.com"
                    },
                    {
                        "name": "Symfony Community",
                        "homepage": "https://symfony.com/contributors"
                    }
                ],
                "description": "Symfony DomCrawler Component",
                "homepage": "https://symfony.com",
                "time": "2017-10-02T06:42:24+00:00"
            },
            {
                "name": "symfony/polyfill-mbstring",
                "version": "v1.6.0",
                "source": {
                    "type": "git",
                    "url": "https://github.com/symfony/polyfill-mbstring.git",
                    "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296"
                },
                "dist": {
                    "type": "zip",
                    "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
                    "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296",
                    "shasum": ""
                },
                "require": {
                    "php": ">=5.3.3"
                },
                "suggest": {
                    "ext-mbstring": "For best performance"
                },
                "type": "library",
                "extra": {
                    "branch-alias": {
                        "dev-master": "1.6-dev"
                    }
                },
                "autoload": {
                    "psr-4": {
                        "Symfony\\Polyfill\\Mbstring\\": ""
                    },
                    "files": [
                        "bootstrap.php"
                    ]
                },
                "notification-url": "https://packagist.org/downloads/",
                "license": [
                    "MIT"
                ],
                "authors": [
                    {
                        "name": "Nicolas Grekas",
                        "email": "p@tchwork.com"
                    },
                    {
                        "name": "Symfony Community",
                        "homepage": "https://symfony.com/contributors"
                    }
                ],
                "description": "Symfony polyfill for the Mbstring extension",
                "homepage": "https://symfony.com",
                "keywords": [
                    "compatibility",
                    "mbstring",
                    "polyfill",
                    "portable",
                    "shim"
                ],
                "time": "2017-10-11T12:05:26+00:00"
            }
        ],
        "packages-dev": [],
        "aliases": [],
        "minimum-stability": "stable",
        "stability-flags": [],
        "prefer-stable": false,
        "prefer-lowest": false,
        "platform": [],
        "platform-dev": []
    }

    Es un json por dentro (Bastante más extenso y complejo que el original) pero su objetivo es el de congelar las dependencias que efectivamente se están usando en tu código.

    Si mirás de nuevo el composer.json vas a notar que la versión de Goutte está expresada en forma explícita (v3.2.0) pero, si mirás un poco más profundamente (por ejemplo en el archivo vendor/fabpot/goutte/composer.json) verás que hay otras que no son tan estrictas:

    {
        "name": "fabpot/goutte",
        "type": "application",
        "description": "A simple PHP Web Scraper",
        "keywords": ["scraper"],
        "homepage": "https://github.com/FriendsOfPHP/Goutte",
        "license": "MIT",
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            }
        ],
        "require": {
            "php": ">=5.5.0",
            "symfony/browser-kit": "~2.1|~3.0",
            "symfony/css-selector": "~2.1|~3.0",
            "symfony/dom-crawler": "~2.1|~3.0",
            "guzzlehttp/guzzle": "^6.0"
        },
        "autoload": {
            "psr-4": { "Goutte\\": "Goutte" }
        },
        "extra": {
            "branch-alias": {
                "dev-master": "3.2-dev"
            }
        }
    }

    Aquí dice que se necesita una versión de Guzzle al menos 6.0.

    Si ves en composer.lock notarás que la instalación de Guzzle es clara:

    ...
            {
                "name": "guzzlehttp/guzzle",
                "version": "6.3.0",
    ...
            }

    Y es que 6.3.0 es al menos 6.0 pero, al momento de distribuir el código necesitamos saber exactamente qué versión estamos usando.

    De modo que el composer.lock se genera:

    1. Recorriendo el árbol de dependencias completo
    2. Especificando las versiones concretas que están instaladas actualmente

    De esta forma, si el código se lleva a un entorno nuevo (o hay que reinstalarlo por alguna razón), la información en composer.lock asegura que se esté usando exactamente el mismo set de dependencias que se usó en testing (Si el testing no fue suficiente es otro tema…).

    Actualización de dependencias

    Por otro lado, si querés probar lo último disponible podés (¡en desarrollo!) ejecutar el comando composer update y composer se encargará nuevamente de descargar las nuevas versiones de tus dependencias (siempre respetando tus especificaciones) para que puedas probarlas y generar un nuevo composer.lock (Que obviamente vas a tener que committear).

    Remoción de dependencias

    Esto puede ser algo más tricky (Siempre es más fácil meter cosas que no se usarán que sacarlas y arriesgarse pero… si sabés lo que hacés…). El comando que se usa para esto es composer remove.

    ¡A usarlo con juicio!

    Y si necesitas ayuda…

    Siempre tenés la posibilidad de ejecutar el comando composer help <comando>.

    Conclusión

    Ahora ya sabés qué es Composer y cómo te puede simplificar la vida.

    La próxima vez que vayas a leer un ejemplo de código comprenderás los pasos de la instalación y podrás leer el archivo composer.json para ver qué otras dependencias vienen incluidas.

    ¿Todavía no descargaste composer?

    ¡Hacelo ahora mismo!

    Acá te dejo el link otra vez, ya vas a tener tiempo de volver acá a agradecerme.