Categoría: Cómo hacer para…

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

  • Cómo resolver el Problema del N+1 en PHP y MySQL

    Cómo resolver el Problema del N+1 en PHP y MySQL

    Qué es el problema del N+1

    Se trata de un problema de optimización de consultas a una base de datos.

    Imagina que tienes una base de datos con dos tablas:

    • Users
    • PhoneNumbers

    Donde cada usuario puede tener muchos números telefónicos (Relación 1:N).

    Ahora, imagina que tienes que mostrar un listado de todos los usuarios junto con sus números de teléfono.

    Una forma de resolverlo sería utilizar una consulta tipo:

    SELECT * FROM users;

    Y luego:

    foreach ($users as $user) {
          $sql = "SELECT * FROM phone_numbers WHERE user_id = {$user->getId()};";
    
         ...
    }

    Lo que sucede aquí es que realizamos una consulta para obtener todos los usuarios y luego, una más por cada uno.

    De ahí que el número total de consultas que se realizan es N (Número de usuarios) + 1.

    Esto puede representar un gran problema cuando N es grande… especialmente cuando el tiempo que tarda cada consulta es significativo (Por ejemplo porque la base de datos está en un servidor remoto y hay que contar con los tiempos de transferencia de red).

    Solución basada en JOIN

    Una primera solución sería utilizar un join:

    SELECT * FROM users u INNER JOIN phone_numbers p ON p.user_id = u.id;

    Aquí el problema es que el resultado tendrá mucha información duplicada (Todos los datos del usuario estarán repetidos en cada fila), lo cual puede traernos inconvenientes con la memoria.

    Solución de dos consultas

    Una solución más eficiente se basa en la realización de dos consultas.

    Los pasos son los siguientes:

    1. Buscar los objetos principales
    2. Obtener los Ids de dichos objetos
    3. Buscar los objetos relacionados para aquellos Ids obtenidos en 2
    4. Mostrar el resultado combinado
    SELECT * FROM user;
    $ids = array_map(function(User $u) { return $u->getId(); }, $users );
    $phone_numbers = $db->query("SELECT * FROM phone_numbers WHERE user_id IN ('.implode(', ', $ids).')");
    
    foreach ( $users as $user ) {
         $str = $user->getName() . implode(',', array_filter($phone_numbers, function($p) use ($user) {
                  return $p['user_id'] == $user->getId();
         }));
    }

    En esta solución la cantidad de consultas realizadas siempre es fija: 2.

    Puede parecer algo más laborioso del lado del PHP, pero ten en cuenta que los tiempos de procesamiento siempre son significativamente inferiores respecto de los de comunicación vía red u otro tipo de operaciones de entrada/salida, con lo cual, vale la pena el esfuerzo extra 😉

  • Cómo ordenar un array multidimensional en PHP

    Cómo ordenar un array multidimensional en PHP

    La estructura de datos más utilizada en PHP es, por lejos, el arreglo.

    Esto se debe a que la implementación de ellos es extremadamente flexible.

    Un problema común que nos encontramos es el ordenarlos.

    Cuando los arreglos son de una única dimensión no hay mucho problema, basta una función como sort, pero cuando el arreglo es una matriz las cosas son un poco más complejas ya que pueden existir diferentes criterios de ordenamiento.

    Por ejemplo, si tu arreglo se ve así:

    [
     [
       'name' => 'Juan',
      'age' => 40,
     ],
       'name' => 'Alberto',
       'age' => 60,
     ],
    ]

    El resultado será diferente si queremos ordenar por age que por name

    Claro que podrías usar una solución diseñada por tí mismo basada en un par de ciclos anidados y seguramente funcionaría, pero sería trabajar de más.

    Ordenar un arreglo PHP según un criterio propio

    Una función muy útil para resolver este tipo de situación es usort.

    Esta función recibe un arreglo como parámetro y una función que se utiliza para comparar cualquier par de elementos entre sí (Lo que se conoce como un callback).

    De este modo, para el ejemplo anterior podríamos definir dos funciones de comparación:

    1. Por nombre
    2. Por edad
    function compareByName(array $elem1, array $elem2) {
        if ( $elem1['name'] > $elem2['name'] ) {
              return 1;
        } elseif ( $elem1['name'] < $elem2['name'] ) {
              return -1;
        } else {
              return 0;
        }
    }
    
    function compareByAge(array $elem1, array $elem2) {
        if ( $elem1['age'] > $elem2['age'] ) {
              return 1;
        } elseif ( $elem1['age'] < $elem2['age'] ) {
              return -1;
        } else {
              return 0;
        }
    }

    Cualquiera de estas podría ser utilizada, según el criterio que necesites, por ejemplo:

    usort($people, 'compareByName');

    O podríamos usar también una función anónima:

    usort($people, function( array $elem1, $elem2 ) {
        if ( $elem1['age'] > $elem2['age'] ) {
              return 1;
        } elseif ( $elem1['age'] < $elem2['age'] ) {
              return -1;
        } else {
              return 0;
        }
    });

    O, si estás usando php7 o superior:

    usort($people, function( array $elem1, $elem2 ) {
        return $elem1['name'] <=> $elem2['name'];
    });

  • Cómo autenticar JWT con PHP de forma segura

    Cómo autenticar JWT con PHP de forma segura

    Un seguidor de Twitter me envía esta pregunta:

    ¿Cuál esería una forma segura de autenticar el jwt que llega en la petición?

    Ante todo gracias Ezequiel y perdona la tardanza en responder.

    Vamos por partes para que se entienda bien de qué estamos hablando.

    Qué es JWT

    JWT son las siglas de JSON Web Tokens. Se trata de una tecnología desarrollada por la empresa Auth0 cuyo objetivo es la transmisión segura de información a través de una red insegura (Internet).

    Como en la mayoría de los mecanismos de seguridad, se trata del envío de información codificada utilizando métodos muy difíciles de descifrar para quien no tenga la información completa pero muy simples para quien sí la posea.

    Desde afuera un token JWT es una cadena de caracteres aleatoria, por ejemplo:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    En realidad, si la miras con algo más de detenimiento notarás que se trata de una cadena particular formada por tres cadenas unidas por un .

    Cuáles son los componentes de un token JWT

    Un token JWT está conformado por:

    1. Un encabezado
    2. La carga útil o payload (El mensaje que se busca transmitir)
    3. Una firma

    El encabezado indica el algoritmo que se utilizará para realizar la codificación y la posterior verificación.

    El payload es una cadena que representa un objeto JSON.

    Por último, la firma se utilizará para darle confiabilidad a la transmisión.

    Dependiendo del algoritmo utilizado esta cadena puede ser una frase cualquiera o un certificado digital.

    Para qué se usa JWT

    Existen muchos casos en los que vale la pena utilizar un token JWT. Uno de los usos más comunes es cuando se busca garantizar la identidad de una entidad que requiere hacer uso de un Servicio Web de nuestra propiedad.

    En general, este mecanismo se adapta mejor que el uso de sesiones estándar.

    Más aún, si la comunicación se realiza entre más de dos actores, JWT ofrece un soporte difícil de lograr con las sesiones.

    Una ventaja clara de JWT contra otros tipos de autenticación es el hecho de que los tokens pueden ser verificados sin necesidad de acceder a información externa (Bases de datos por ejemplo).

    Un punto muy importante a tener en cuenta cuando se usan tokens JWT es que la información transportada no se encuentra encriptada. Sólo la firma lo está.

    Esto permite garantizar que la información transportada no ha sido alterada durante la transmisión, pero no puede garantizar que un agente malicioso la haya leído.

    Cómo leer un token JWT usando PHP

    En el caso de la pregunta que realiza Ezequiel vale el supuesto de que el token ha sido generado por algún tercero del cual se pretende validar su autenticidad.

    Para ello existen, además de la posibilidad de descifrar el token manualmente, una serie de librerías que se detallan en el sitio de Auth0. Dado que se trata de un estándar abierto cualquiera puede crear su propia implementación.

    Tomaré como base de este ejemplo este artículo publicado en el propio sitio de Auth0.

    El primer paso para trabajar con un token JWT es verificar su autenticidad.

    Cómo verificar la autenticidad de un token JWT usando PHP

    Para verificar la autenticidad del token se requiere calcular la firma y validarla contra la recibida.

    Si coinciden se puede asumir que el mensaje no ha sido alterado por terceros, en caso contrario el token debe ser rechazado.

    Podemos realizar esta verificación utilizando este código:

    <?php
    require 'bootstrap.php';
    use Carbon\Carbon;
    
    // get the local secret key
    $secret = getenv('SECRET');
    
    if (! isset($argv[1])) {
        exit('Please provide a key to verify');
    }
    
    $jwt = $argv[1];
    
    // split the token
    $tokenParts = explode('.', $jwt);
    $header = base64_decode($tokenParts[0]);
    $payload = base64_decode($tokenParts[1]);
    $signatureProvided = $tokenParts[2];
    
    // check the expiration time - note this will cause an error if there is no 'exp' claim in the token
    $expiration = Carbon::createFromTimestamp(json_decode($payload)->exp);
    $tokenExpired = (Carbon::now()->diffInSeconds($expiration, false) < 0);
    
    // build a signature based on the header and payload using the secret
    $base64UrlHeader = base64UrlEncode($header);
    $base64UrlPayload = base64UrlEncode($payload);
    $signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
    $base64UrlSignature = base64UrlEncode($signature);
    
    // verify it matches the signature provided in the token
    $signatureValid = ($base64UrlSignature === $signatureProvided);
    
    echo "Header:\n" . $header . "\n";
    echo "Payload:\n" . $payload . "\n";
    
    if ($tokenExpired) {
        echo "Token has expired.\n";
    } else {
        echo "Token has not expired yet.\n";
    }
    
    if ($signatureValid) {
        echo "The signature is valid.\n";
    } else {
        echo "The signature is NOT valid\n";
    }

    Para que este código pueda ejecutarse será necesario crear un archivo composer.json con este contenido:

    {
      "require": {
        "vlucas/phpdotenv": "^2.4",
        "nesbot/carbon": ">=2.61.0"
      },
      "autoload": {
        "psr-4": {
          "Src\\": "src/"
        }
      }
    }

    Y ejecutar el comando composer install para instalar todas las dependencias usando composer.

    Por último, es necesario definir la clave secreta en un archivo .env por ejemplo:

    SECRET=Th1s1sMyS3cr3t!

    De esta forma será posible validar cualquier token que haya sido generado utilizando esta clave secreta.

    Puedes probar esto entrando en la página de JWT e ingresando estos datos:

    Con eso puedes copiar el texto en el cuadro «Encoded» y ejecutar el script de validación de esta forma:

    php validate_jwt.php eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNTE2MjM5MDIyfQ.xSRMoZpDHCAREK9x0ELx5yL8Y0qkpPt3eVrK9ItiyUo

    Lo cual te dará un resultado como este:

    Header:
    {"alg":"HS256","typ":"JWT"}
    Payload:
    {"sub":"1234567890","name":"John Doe","exp":1516239022}
    Token has expired.
    The signature is valid.

    Con lo cual, se ha confirmado la autenticidad del token recibido.

    Algunas librerías JWT para PHP

    Esta verificación se realizó analizando manualmente el token y, si bien esto es perfectamente posible, no es lo más conveniente en un ambiente de producción (Principalmente por motivos de mantenibilidad a largo plazo).

    Algo más recomendable es la utilización de alguna librería.

    Una bastante simple es la desarrollada por Firebase.

    Una algo más compleja pero muy interesante para usos avanzados es la implementación de Spomky.

    https://www.techiediaries.com/php-jwt-authentication-tutorial/

  • Cómo migrar un sitio web sin interrumpir el servicio

    Cómo migrar un sitio web sin interrumpir el servicio

    La tarea de migrar un sitio web no es particularmente sencilla.

    Obviamente, no todos los sitios tienen la misma infraestructura, con lo cual, lo que te voy a contar no necesariamente aplica a tu caso, pero espero que te lleves algunas ideas que puedan ayudarte.

    Voy a suponer por el momento que tu sitio tiene los componentes típicos:

    1. Una base de datos
    2. Un paquete de código

    El hecho de que tu sitio esté online significa que existe, al menos, un servidor donde está alojado.

    Y por último, hay un par de componentes muy importantes si el sitio es accesible para todo público:

    1. Un dominio
    2. Una serie de registros de DNS

    Supongamos que el servidor donde está la base de datos no es el mismo que donde reside la aplicación, es decir, lo que querés hacer en principio es migrar tu código a algún otro servidor (dentro o fuera del hosting actual).

    Mi recomendación es que antes de migrar hagas un relevamiento detallado de qué infraestructura estás usando (y, esperablemente, está funcionando):

    • ¿Qué versión de PHP estás usando? ¿Qué paquetes tiene instalado?
    • ¿Qué tipo de webserver? ¿Qué versión?
    • ¿Qué motor de base de datos? ¿Qué versión?
    • ¿Qué permisos tienen los directorios de la aplicación?
    • Etc…

    Una vez tengas claro este panorama, lo siguiente es armar un plan de pruebas básico que permita detectar rápidamente si algo no quedó 100% bien durante el traspaso.

    No hay que hacer mucho acá, sólo saber cuáles son las funcionalidades críticas del sitio y qué experimentos permitirán determinar que están ok.

    La estrategia general va a ser clonar tu sitio actual y, por un tiempo lo más breve posible, tener el original y el clon conviviendo lo más armoniosamente que se pueda… y luego matar al clon.

    Por qué migrar de servidor

    Empecemos pensando en qué razones tendrías para migrar de servidor.

    Se me ocurren varias:

    • Conseguiste uno más barato
    • El hosting actual no te da el soporte que necesitás
    • El hardware te quedó chico

    O, como le ocurrió a un cliente mío recientemente, el tráfico te superó y tenés que implementar balanceo de carga.

    Cómo migrar el código de tu sitio

    Migrar el código suele ser la parte más fácil.

    Si lo tenés versionado simplemente se trata de hacer un checkout (o un git clone o similar) en tu nuevo servidor.

    Si no lo tenés versionado vas a necesitar subirlo de alguna forma… ftp, scp, rsync… la que prefieras.

    Una vez tengas el código presente en tu nuevo servidor te va a tocar modificar las configuraciones para dejar andando.

    Algo a lo que tenés que prestar atención es que el usuario de tu aplicación esté habilitado para realizar consultas desde la nueva IP (Al menos en el caso de MySQL esto puede ser un problema).

    Cómo migrar la configuración de tu sitio

    Nuevamente, esto dependerá en gran medida de cómo lo tengas montado actualmente.

    Lo ideal sería que la dependencia respecto del webserver sea mínima para evitar problemas de incompatibilidades.

    Por ejemplo, algo que suele hacerse con los sitios montados sobre Apache es usar el archivo .htaccess… claramente, si vas a migrar hacia un servidor con NginX la cosa se va a complicar.

    Si la configuración no está versionada vas a tener que asegurarte de que el hosting de destino tenga la misma configuración (idealmente) o que las configuraciones del nuevo servidor sean compatibles con tu aplicación.

    Cómo migrar un dominio a un nuevo servidor

    Acá se trata de cambiar los registros de DNS para que apunten a la IP del nuevo servidor.

    En tu servidor de nombres vas a encontrar un registro tipo A que apunta a la IP de tu servidor actual (Y tal vez encuentres uno tipo AAAA con la dirección IPv6).

    Cuando cambies ese valor cada vez que alguien escriba http://tusitio.com ingresará al servidor que responda a la nueva IP.

    El problema es que los cambios de DNS no son inmediatos (Pueden tomar hasta 48 hs!).

    Y otro pequeño problema es cómo probar todo antes de darle al botón rojo.

    Un truco que podés usar es hacer el cambio en forma local antes de tocar los DNS.

    El tema es simple: tu computadora tiene una serie de definiciones de nombres en un archivo y esa definición toma prioridad sobre cualquier otra información que se encuentre en Internet (Por un tema de optimización principalmente).

    Ese archivo está en /etc/hosts si usás Linux o similar o en C:\Windows\System32\drivers\etc\hosts si usás Windows.

    Se ve más o menos así:

    Lo que podés hacer es agregar una línea tipo:

    XX.YY.ZZ.WW     tusitio.com

    Y a partir de ahí, cualquier petición que hagas, no importa si es a través de un navegador, cURL o lo que fuera, va a ir a la IP XX.YY.ZZ.WW (No importa lo que diga el DNS).

    Una vez hayas hecho todas las pruebas y tengas confianza en que todo está en su lugar podrás efectivizar el cambio de los DNS.

    Por precaución te recomiendo no dar de baja el hosting original por un tiempo prudencial (Una semana, un mes… depende qué tan conservador quieras ser) pero claramente la prueba de fuego llegará cuando lo hagas 🙂

    En conclusión: no es una magia oculta pero para no tener sorpresas desagradables más vale dedicar algo de tiempo a planificar la mudanza.

  • Cómo armar una tabla pivot con PHP y MySQL

    Cómo armar una tabla pivot con PHP y MySQL

    Hurgando en las profundidades de la Internet me encontré con una pregunta muy interesante.

    El autor comentaba que tenía una tabla con esta pinta:

    Y quería, mediante una consulta a MySQL, obtener un resultado de este tipo:

    Básicamente el desafío era transformar datos horizontales (los valores de la columna «endpoint» para cada fila) en verticales, es decir, columnas de la respuesta.

    No es algo que se vea todos los días, cierto, pero… ¿cómo rechazar un desafío semejante? 🙂

    Inmediatamente me vino a la mente el concepto de Tabla Pivot que manejan las planillas de cálculo.

    Así que me arremangué y escribí este SQL:

    SELECT FROM_UNIXTIME(FLOOR((UNIX_TIMESTAMP(fecha_hora))/60)*60) as fecha_hora,
        SUM(
    		CASE
    		  WHEN endpoint = 50
    		   THEN numero_personas
    		   ELSE 0
    		END
        ) as suma_personas_ep50,
        SUM(
    		CASE
    			WHEN endpoint = 51
    			THEN numero_personas
    			ELSE 0
    		END
        ) as suma_personas_ep51
    	FROM t
    	GROUP BY FROM_UNIXTIME(FLOOR((UNIX_TIMESTAMP(fecha_hora))/60)*60)
    	;

    Con esto obtuve el resultado esperado. ¡Exito!

    Pero… inmediatamente surgió la pregunta en mi cabeza: ¿Qué pasará cuando se agreguen nuevos endpoints?

    Pues… ¡usemos un poco de PHP!

    <?php
    
    try {
    	$pdo = new PDO("mysql:dbname=escuelait;host=localhost", "root", "1234");
    } catch ( PDOException $e ) {
    	die ( $e->getMessage() );
    }
    
    $sql = "SELECT DISTINCT(endpoint) FROM t;";
    
    $endPoints = $pdo->query($sql, PDO::FETCH_COLUMN,0)->fetchAll();
    
    $templateSQL = "SUM(
    		CASE
    		  WHEN endpoint = |ENDPOINT|
    		   THEN numero_personas
    		   ELSE 0
    		END
        ) as suma_personas_ep|ENDPOINT|";
    	
    $endPointsSQL = implode( ",".PHP_EOL, array_map( function( $endPoint ) use ( $templateSQL ) {
    	
    	return preg_replace( '/\|ENDPOINT\|/', $endPoint, $templateSQL ) ;
    }, $endPoints ) );
    
    $finalSQL = "SELECT FROM_UNIXTIME(FLOOR((UNIX_TIMESTAMP(fecha_hora))/60)*60) as fecha_hora,
        $endPointsSQL
    	FROM t
    	GROUP BY FROM_UNIXTIME(FLOOR((UNIX_TIMESTAMP(fecha_hora))/60)*60)
    	;";
    	
    echo $finalSQL.PHP_EOL;

    Este script realiza dos consultas:

    1. La primera para obtener los endpoints existentes
    2. La segunda para generar la tabla dinámica

    El truco está en generar el string SQL en forma dinámica.

    No es la sintaxis más utilizada pero… puede serte útil alguna vez 😉

  • 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).

  • Cómo usar URLs amigables con el Servidor Web Incorporado a PHP

    Cómo usar URLs amigables con el Servidor Web Incorporado a PHP

    Es muy común, desde la versión 5.4 de PHP, usar el servidor que viene incorporado mientras estamos en un ambiente de desarrollo (¿Para qué negarlo? ¡Es sumamente cómodo!).

    Un problema que sucede a menudo al utilizarlo es cómo usar URLs amigables.

    Por ejemplo, a un sitio web productivo no vas a querer que se acceda mediante algo como:

    https://misitio.com/index.php?fecha=2019-06-25&slug_categoria=top10

    Más bien vas a preferir algo como:

    https://misitio.com/articulos/2019-06-25/top10

    ¿O no?

    El problema es que, en el caso de PHP, las variables que se reciben a través de la URL son accesibles a los scripts a través de la variable $_GET.

    Claro que, para que esto suceda, estas URLs deben estar escritas respetando el formato canónico:

    1. La separación entre URLs y parámetros se marca con el caracter «?»
    2. La separación entre nombre del parámetro y valor se marca con el caracter «=»
    3. La separación entre valor de un parámetro y nombre del siguiente se marca con el caracter «&»

    La solución a este problema pasa por aplicar lo que se conoce como reglas de re-escritura.

    Qué son las reglas de re-escritura

    Las reglas de re-escritura son instrucciones que se le dan al servidor web para transformar URLs agradables en otras más útiles.

    Básicamente se trata de reglas de mapeo que establecen a qué URL real le corresponde cada URL amigable.

    Usualmente estas reglas se escriben utilizando expresiones regulares (Claro que la sintaxis exacta dependerá del software de servidor web que estés utilizando).

    Por ejemplo, una regla de re-escritura de Apache se verá de esta forma:

    RewriteRule .? http://www.example.com%{REQUEST_URI} [R=301,L] 

    En un entorno productivo, las reglas de re-escritura se escriben en algún archivo de configuración del webserver, en desarrollo en cambio, el tema puede no ser tan fácil…

    Cómo usar reglas de re-escritura con el servidor incorporado a PHP

    El servidor web incorporado a PHP no tiene un mecanismo explícito de re-escritura de URLs, sin embargo, es perfectamente posible el uso de URLs amigables.

    El truco es iniciar el servidor web dirigiendo todo el tráfico hacia un archivo en particular.

    Por ejemplo:

    php -S localhost:8000 router.php

    Al hacer esto, todos los pedidos que lleguen a http://localhost:8000 serán atendidos por el archivo router.php.

    Nuestra tarea ahora es escribir el código dentro del archivo de modo tal que realice las transformaciones necesarias.

    Para ello vamos a apoyarnos en:

    1. La variable $_SERVER[«REQUEST_URI»]
    2. La función preg_match

    De la variable $_SERVER[«REQUEST_URI»] obtendremos la URL tal como la escribió el usuario.

    Con la función preg_match validaremos si dicha URL tiene una cierta forma y, de ser así, redirigiremos la petición a donde correponda.

    Veamos un ejemplo de router:

    <?php
    
    $matches = $_GET = [];
    
    if (preg_match('/\/([^\/]+)\/([^\/]+)/', $_SERVER["REQUEST_URI"], $matches)) {
        $_GET['resource_type'] = $matches[1];
        $_GET['resource_id'] = $matches[2];
    
        error_log( print_r($matches, 1) );
        require 'server.php';
    } elseif ( preg_match('/\/([^\/]+)\/?/', $_SERVER["REQUEST_URI"], $matches) ) {
        $_GET['resource_type'] = $matches[1];
        error_log( print_r($matches, 1) );
    
        require 'server.php';
    } else {
    
        error_log('No matches');
        http_response_code( 404 );
    }

    En este caso estoy usando la expresión regular /\/([^\/]+)\/([^\/]+)/ para buscar URLs que tengan la forma /algo/otraCosa (O, más concreto: /articulos/deportes) y la expresión /\/([^\/]+)\/?/ para URLs de tipo /algo.

    Si la URL efectivamente coincide con alguno de los patrones buscados tomaré las coincidencias y con eso llenaré el arreglo $_GET lo cual, en definitiva logra la transformación de fragmento de URL en parámetro.

    Y por último, realizaré el require para delegar el control en el archivo que efectivamente sabrá cómo procesar este request.

    No está mal, ¿cierto? 🙂

    Un tip extra, por si te estás enloqueciendo con las expresiones regulares, me ayudó mucho usar este verificador: https://regex101.com/

  • Cómo pasar una variable de JavaScript a PHP

    Cómo pasar una variable de JavaScript a PHP

    Si estás programando algún sistema web medianamente complejo, es muy probable que te hayas enfrentado a este problema alguna vez.

    En muy resumidas cuentas, lo que estás intentando hacer es algo como:

    var variable_js = 2;
    $variable_php = variable_js;

    Sería lindo que todo funcionara de esa forma, ¿no? Lamentablemente, la cosa no es tan fácil (Pero tampoco es tan difícil en realidad).

    Por qué no se puede pasar directamente un valor de Js a PHP

    Esta pregunta esconde un poco de confusión respecto de cómo funcionan las aplicaciones web. Algo parecido a lo que comentaba en este artículo.

    El punto es que PHP y JavaScript se ejecutan en lugares y momentos diferentes.

    Es como si estuvieses leyendo un libro y te encontraras con algo que no comprendés del todo y para solucionarlo le hicieras la pregunta al libro… algo no va a funcionar.

    Empecemos entonces por comprender el ciclo de vida de una aplicación web.

    Cómo es el ciclo de vida de una aplicación web

    El flujo típico de una aplicación web es el siguiente:

    1. El cliente hace un pedido al servidor (Envía un comando HTTP)
    2. El servidor lo recibe y lo analiza
      1. Si se trata de un php se lo pasa al intérprete correspondiente
      2. El intérprete procesa el código y genera una salida (Usualmente HTML)
    3. El servidor envía la salida hacia el cliente
    4. El cliente analiza la respuesta y dibuja la página
    5. El cliente ejecuta el código Js

    Entonces, si observás este ciclo, te darás cuenta que, para el momento en que la variable Js aparece en escena… php ya está haciendo cualquier otra cosa.

    Pues entonces… ¿no hay solución? ¡No tan rápido!

    Lo que necesitas es poder ejecutar JS y PHP en paralelo (es decir, en un mismo tiempo).

    Cómo enviar información de JavaScript a PHP

    La solución al problema pasa por usar un poco de imaginación… y ajax :).

    Cómo funciona una aplicación con AJAX:

    1. El cliente hace un pedido al servidor (Envía un comando HTTP)
    2. El servidor lo recibe y lo analiza
      1. Si se trata de un php se lo pasa al intérprete correspondiente
      2. El intérprete procesa el código y genera una salida (Usualmente HTML)
    3. El servidor envía la salida hacia el cliente
    4. El cliente analiza la respuesta y dibuja la página
    5. El cliente ejecuta el código Js
      1. Dentro del código se ejecuta una segunda petición al servidor (la cual tiene su propio ciclo de vida que culmina con una respuesta HTTP).
      2. El cliente recibe la respuesta del servidor
      3. El cliente actúa sobre la respuesta recibida

    Dependiendo de cuál sea tu necesidad puntual (Es decir, qué necesitas que la aplicación haga una vez reciba la variable de JS), deberás determinar si el cliente debe bloquearse (Es decir, no hacer nada hasta que PHP complete su procesamiento de la información enviada) o puede continuar con alguna otra tarea.

    Un ejemplo de esto sería:

    <?php
    $var = 1;
    ?>
    <html>
    	<body>
    		<input type="button" value="Enviar variable" id="send"/>
    	</body>
    	<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    	<script type="text/javascript">
    	$('#send').click( function() {
    	alert('Enviando!');
    		$.ajax(
    				{
    					url: 'get_var.php?var=<?php echo $var; ?>',
    					success: function( data ) {
    						alert( 'El servidor devolvio "' + data + '"' );
    					}
    				}
    			)
    		}
    	);
    	</script>
    </html>

    Y el get_var.php:

    <?php
    echo 'Recibi '.$_GET['var'];

    Y de ese modo se puede comunicar JavaScript hacia PHP.

  • Cómo debuggear un webservice hecho con PHP

    Cómo debuggear un webservice hecho con PHP

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

    Por qué es complejo debuggear un WebService

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

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

    Cómo simular una petición a un WebService

    Una herramienta muy útil para esto es cURL.

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

    Por ejemplo:

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

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

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

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

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

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

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

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

    Cómo debugear un pedido HTTP

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

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

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

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

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

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

    Esto puede funcionar aunque no es muy eficiente que digamos 😉

    ¿Entonces qué podemos hacer?

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

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

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

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

  • Cómo hacer un autocomplete con PHP

    Cómo hacer un autocomplete con PHP

    Es muy común hoy en día encontrarnos con formularios que deben completarse mediante alguna opción pre-existente en el sistema.

    Si las opciones son pocas, lo más usual es utilizar un dropdown (un objeto basado en el tag select de HTML), pero si la cantidad de opciones es grande, esto puede volverse un fastidio para el usuario.

    Una forma mejor es dejar que el usuario ingrese el texto que quiera y dejar que el sistema autocomplete el resto.

    Para lograr este efecto se requiere una combinación de factores:

    1. Un servicio que pueda tomar el texto introducido por el usuario y devuelva una lista de opciones disponibles que coincidan.
    2. Una página capaz de tomar la entrada del usuario, interactuar con el servicio y presentar las opciones al usuario.

    Un servicio que busque opciones que coincidan con el texto que ingresó el usuario

    Vamos a comenzar por diseñar un script php que resuelva esta parte del problema:

    <?php
    
    $options = [
            "ActionScript",
          "AppleScript",
          "Asp",
          "BASIC",
          "C",
          "C++",
          "Clojure",
          "COBOL",
          "ColdFusion",
          "Erlang",
          "Fortran",
          "Groovy",
          "Haskell",
          "Java",
          "JavaScript",
          "Lisp",
          "Perl",
          "PHP",
          "Python",
          "Ruby",
          "Scala",
          "Scheme"
    ];
    
    if ( $term = $_GET['term'] ?? '' ) {
            $matches = array_filter( $options, function( $option ) use ( $term ) {
                    return strpos( strtolower($option), $term ) !== false;
            } );
    
            header( 'Content-Type: text/javascript' );
            echo json_encode( array_values($matches) );
    }

    A este pequeño script le llamaremos get_matches.php.

    Un formulario que use el autocomplete

    La segunda parte de la solución es un formulario capaz de tomar la entrada del usuario, enviársela al servicio y actuar según la respuesta recibida.

    Se verá de este modo:

    <html>
    <body>
    <form>
    	<p><label for="language">Language</label><input type="text" name="language" id="language"/></p>
    	<p><select id="multiple" style="display: none;" size="10"/></p>
    </form>
    </body>
    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
      <script>
    	$( "#language" ).on( 'input', function() {
    			$('#multiple').hide();
    			$.ajax(
    				{
    					url: 'get_matches.php?term=' + $(this).val(),
    					success: function( data ) {
    						if ( data.length == 1 ) {
    							$('#language').val( data[0] );
    						} else {
    							if ( data.length > 1 ) {
    								$("#multiple").find('option').remove();
    								data.forEach( function(e) {
    									$("#multiple").append("<option>" + e + "</option>");
    								});
    								$("#multiple").prop( 'size', data.length );
    								$("#multiple").show();
    							}
    						} 
    					},
    					dataType: 'json',
    				}
    			);
    		}
    	);
    	
    	$( "#multiple" ).click( function() {
    		$('#language').val( $(this).val() );
    		$('#multiple').hide();
    	}
    	);
      </script>
    </html>

    Para verlo todo funcionando basta con iniciar el servidor incorporado a PHP:

    php -S localhost:8000

    Y abrir un navegador en localhost:8000 para ver:

    Y luego, a medida que vayamos escribiendo veremos aparecer opciones:

    Y cuando hayamos escrito una opción que acota la lista a un solo candidato automáticamente se llenará el campo.

    Algunas notas

    Habrás notado que, en realidad, la magia se hace del lado del frontend (¡Mucho javascipt!).

    Lo que se podría (y probablemente debería!) modificarse es el modo de conseguir las opciones desde PHP.

    En este ejemplo usamos un arreglo estático, pero en un caso más real lo haríamos a través de alguna consulta a una base de datos, algo como:

    SELECT name FROM languages WHERE name LIKE '%TERM%';

    Reemplazando TERM por lo que haya ingresado el usuario.

    Esto funcionará bien mientras la tabla sobre la que queramos buscar no sea extremadamente grande (Unos pocos miles de registros debería soportar sin mucho problema)… si la base es más grande o queremos hacer búsquedas menos exactas deberemos utilizar alguna otra herramienta como Solr, Sphinx o ElasticSearch.