Blog

  • Cuándo usar una clase abstracta y cuándo una interface

    Cuándo usar una clase abstracta y cuándo una interface

    Un lector de mi libro sobre Programación Orientada a Objetos con PHP me envía esta pregunta a través de LinkedIn:

    Empecé a responderle a su mensaje pero luego se me ocurrió que sería mejor aprovechar y contestarlo en público así que aquí voy.

    Empecemos por comprender de qué se trata cada uno.

    Qué es una clase abstracta

    En su definición más cruda una clase se dice abstracta si no es posible utilizarla para crear objetos (instancias).

    Suena un poco raro, ¿no? ¿Para qué quiero tener una clase si no es para crear instancias?

    La explicación viene asociada al concepto de Herencia (Tema para otro artículo en todo caso).

    Una clase abstracta puede usarse como base de una jerarquía.

    Se define de esta forma:

    <?php
    
    abstract class Abstracta
    {
        public function metodoConcreto()
        {
            return true;
        }
    
        abstract public function metodoAbstracto();
    }

    Notá cómo el método metodoConcreto() tiene definición (está el cuerpo completo) y como en cambio, de metodoAbstracto() sólo está su declaración (Aparte de llevar la palabra «abstract» como modificador).

    Para usarla en una clase concreta se necesita que la hija complete las definiciones que su padre (o alguno de sus ancestros) han dejado inconclusas:

    class Concreta extends Abstracta
    {
        public function metodoAbstracto()
        {
            return true;
        }
    }

    En la vida real, este tipo de estructura viene muy bien para implementar, por ejemplo, hooks.

    La idea será algo como esto: la clase padre (abstracta) define una serie de operaciones bastante complejas y repetitivas y deja una o dos funciones sin definir para que la clase hija escriba aquí sus particularidades.

    Un ejemplo que me viene a la mente es un ORM basado en ActiveRecord.

    En este caso, habría una clase Record que tendría un método save y podría tener algún método tipo preSave/postSave para que cada tipo de registro en particular pueda intercalar validaciones u operaciones encadenadas.

    Qué es una interface

    Una interface puede definirse como una declaración de métodos abstractos.

    En este sentido se parece a una clase abstracta… la diferencia (a simple vista al menos) es que una interface no puede definir métodos (Sólo puede declararlos).

    Se ve de esta forma:

    interface UnaInterface
    {
        public function f1();
        
        public function f2();
    }

    Según los teóricos más puristas de la Programación Orientada a Objetos toda clase debería implementar al menos una interface.

    Por lo general se utilizan interfaces cuando se quiere unificar nombres de métodos pero seguir manteniendo comportamientos que no tienen nada que ver uno con el otro.

    De hecho, las interfaces suelen utilizarse como factor común entre clases que no pertenencen a una misma jerarquía.

    Por ejemplo, si tomamos una clase Book y otra clase Invoice sería difícil establecer una relación jerárquica entre ellas (Ni Book es un tipo especial de Invoice ni viceversa).

    Sin embargo, es muy probable que ambas clases se beneficien de contar con un método print, aunque la forma específica de responder a esa llamada (Es decir, la forma de imprimir) será muy diferente para cada uno de ellos.

    Habiendo clases abstractas… ¿por qué se necesitan interfaces?

    La respuesta a esta pregunta tiene que ver con algunos problemas de implementación.

    En algunos lenguajes (C++ por ejemplo) existe lo que se conoce como Herencia Múltiple (La posibilidad de que una clase tenga más de un antecesor directo).

    Es decir, el esquema se vería algo como:

    Se ve que la clase TA hereda de Student Y de Faculty. Hasta ahí no hay problema… pero ¿qué pasa si Student define un método con el mismo nombre que Faculty?

    Cuando se invoque $ta->metodo() ¿cuál debería ejecutarse? (asumiendo por supuesto que TA no tiene una definición propia de ese método).

    Una forma elegante de resolver este problema es impedir la herencia múltiple (Eso es lo que hacen, entre otros, Java y PHP).

    Pero la necesidad de reutilizar nombres (y comportamientos) a lo largo de diferentes jerarquías de clases no desaparece… de ahí surge la idea de las interfaces (y los traits… tema para otro artículo).

    Un punto importante: una clase (en PHP al menos) puede implementar tantas interfaces como se desee (pero sólo puede extender una clase base).

    Cómo decidir si conviene una clase abstracta o una interface

    Pues bien, ahora sí estamos listos para responder a la pregunta original 🙂

    Debe usarse una clase abstracta cuando se está modelando una jerarquía de clases y una interface cuando se pretende homogeneizar nombres entre objetos que no están emparentados. Compartir en X

    Esto parece una obviedad, pero no es tan así… a veces nos encontramos con objetos que parecen estar relacionados mediante herencia pero en realidad no es así.

    Para determinar si este es el caso vale preguntarse: «¿Es este objeto un caso particular de sus predecesores?» (Un auto es un vehículo).

    El uso de interfaces nos permite «olvidar» momentáneamente con qué tipo de objetos estoy trabajando.

    En esto se basa el principio de segregación de la interface (La I de SOLID)

    Un ejemplo que me viene a la mente es algo que utilizamos hace unos años para una red social de viajes en la que trabajaba.

    Originalmente esta red social permitía a sus usuarios subir sus fotos y diarios de viaje (compartir sus experiencias en formato similar a un blog).

    Una foto y un diario de viaje tenían bastante pocas similitudes (De hecho las clases que las representaban no tenían ningún ancestro en común).

    Un día surgió la necesidad de dotar al sistema de la posibilidad de que otros usuarios dejaran sus opiniones sobre las fotos y/o los diarios de los demás.

    Entonces se nos ocurrió agregarle a la clase usuario un método opinar().

    Pero no podíamos crear un método opinarSobreFoto( Foto $foto ) y otro opinarSobreDiario( Diario $diario )… en rigor de verdad podríamos haberlo hecho pero no era para nada mantenible.

    Una forma mejor de resolverlo fue crear una interface Opinable que tanto la clase Foto como la clase Diario implementaran y luego el método Usuario::opinar pudiera usar, dando lugar a algo como:

    <?php
    
    class Usuario
    {
       public function opinar( Opinable $opinable, string $texto )
       {
           return new Opinion( $opinable, $this, $texto ); 
       }
    } 

    De esta forma, agregar otro opinable (Por ejemplo, un destino visitado) no supone ningún problema para la clase Usuario.

    Y así todos felices 🙂

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

  • Un dashboard en tiempo real basado en PHP y Bootstrap

    Un dashboard en tiempo real basado en PHP y Bootstrap

    Algo que está muy de moda por estos días es la creación de tableros de comandos (Dashboards) que se mantengan actualizados en tiempo real.

    Si bien la definición de tiempo real es algo vaga (Formalmente se trata de sistemas en los cuales el tiempo de respuesta es crítico), hay una suerte de conocimiento en común respecto de lo que quiere decir: que los cambios se vean en forma inmediata (o casi).

    En lo que hace a aplicaciones web, de lo que se trata es de permitir al visitante recibir novedades sin tener que recargar la página.

    Para lograr este efecto se necesitan dos partes coordinadas:

    1. Una aplicación front-end con la que el usuario interactuará en forma directa
    2. Un servidor que mantenga la información actualizada en todo momento

    Un frontend para el Dashboard

    Del lado del frontend, lo que necesitaremos serán dos cosas:

    1. Una capa de presentación (HTML + CSS)
    2. Una capa de interacción con el backend (JavaScript)

    Presentación del dashboard

    Del lado de la presentación, dado que se trata de un dashboard, lo que querremos hacer es mostrar información resumida, en algún formato fácil de interpretar por una persona (Usualmente se tratará de gráficos pero podemos usar cualquier forma que nos parezca adecuada).

    Una librería que resulta muy útil para realizar este tipo de tareas es Bootstrap (Que, de paso, nos da una visualización estándar).

    En este ejemplo haré un dashboard muy sencillo, sólo a modo de ilustración, pero con los mismos principios puedes hacer otros mucho más vistosos.

    Veamos el HTML:

    <!doctype html>
    <html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    
        <title>Hello, world!</title>
    </head>
    <body>
    <h1>Hola, este es mi dashboard</h1>
    <div class="container">
        <div class="row">
            <div class="col-sm">
                <div class="card">
                    <div class="card-body">
                        Usuarios conectados en este momento: <strong><span id="connected_users">0</span></strong>
                    </div>
                </div>
            </div>
            <div class="col-sm">
                <div class="card">
                    <div class="card-body">
                        Facturación del día: <strong>$ <span id="daily_revenue">0.00</span></strong>
                    </div>
                </div>
            </div>
        </div>
    </div>
    </body>
    </html>

    Aquí lo que estoy haciendo es crear el esqueleto de mi dashboard.

    Para el ejemplo tomé dos métricas que pueden resultar interesantes:

    • Cantidad de usuarios conectados
    • Facturación del día

    La pantalla inicial se verá así:


    Si te fijas, el tag link (en la sección head) incluye el CSS que necesito para usar Bootstrap.

    Interacción con el backend del dashboard

    El siguiente paso es agregar la interacción con el backend (¡Que aún no está hecho! Cierto… no nos adelantemos, al final del artículo tendrás todo lo que buscas :).

    Lo primero es incluir la librería jQuery:

    <script
            src="https://code.jquery.com/jquery-3.4.1.min.js"
            integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
            crossorigin="anonymous"></script>

    Podría escribir todo el JavaScript necesario sin utilizarla, es cierto, pero sería muchísimo más trabajo del necesario y, honestamente, prefiero evitármelo :p

    Algo que puedes notar es que estoy incluyendo la librería desde otro servidor (code.jquery.com, un CDN).

    Y ahora me queda incluir algo de código mío (el que efectivamente interactuará con mi servidor):

    <script type="text/javascript">
        window.setInterval(function () {
            updateStats();
        }, 2000);
    
        function updateStats() {
            updateConnectedUsers();
            updateDailyRevenue();
        }
    
        function updateConnectedUsers() {
            jQuery.get(
                'get_connected_users.php',
                function (data) {
                    $('#connected_users').text(data);
                }
            );
        }
    
        function updateDailyRevenue() {
            jQuery.get(
                'get_daily_revenue.php',
                function (data) {
                    $('#daily_revenue').text(data);
                }
            );
        }
    </script>

    Aquí lo que estoy haciendo es generar un mecanismo de polling de modo que el cliente esté constantemente pidiendo actualizaciones al servidor (y reflejándolas en el HTML para el visitante).

    Muy bien, sólo nos queda ver el backend 🙂

    Un backend para el dashboard

    Del lado del backend sólo debo crear los archivos que puedan responder a las preguntas del frontend (get_connected_users.php y get_daily_revenue.php).

    En mi ejemplo haré algo de trampa, no voy a consultar a una base de datos ni nada, simplemente generaré valores aleatorios, pero claramente, en una aplicación real, deberías usar algún tipo de almacenamiento.

    get_connected_users.php:

    <?php
    
    header('Content-Type: text/javascript');
    echo rand(0, 200);

    get_daily_revenue.php:

    <?php
    
    session_start();
    
    $acummulatedRevenue = $_SESSION['acummulatedRevenue'] ?? 0;
    
    header('Content-Type: text/javascript');
    
    $_SESSION['acummulatedRevenue'] = $acummulatedRevenue + rand( 0, 100000 ) / 100;
    
    echo number_format( $_SESSION['acummulatedRevenue'], 2, ',', '.' );

    Lo importante de ambos scripts es la línea header(‘Content-Type: text/javascript’);.

    Esto es lo que hace que la llamada Ajax entienda que lo que recibe del servidor es, efectivamente, texto jSON

    Dashboards más profesionales

    Con lo que te mostré hasta aquí tienes todo lo necesario para hacer un dashboard básico (y bastante poco estético :p).

    Ciertamente existen plantillas de dashboards mucho más atractivas que puedes usar (o ser creativo y crear las tuyas!), algunas que puedes probar:

    ¡Ya estás list@ para agregar bonitos dashboards a tus aplicaciones! ¡Adelante!

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

  • Cómo instalar librerías de composer en un hosting compartido

    Cómo instalar librerías de composer en un hosting compartido

    Una pregunta que me han hecho en repetidas oportunidades es cómo usar composer en un ambiente de hosting compartido.

    Personalmente, siempre prefiero usar mis propios servidores tipo VPS, precisamente para evitar este tipo de problemas, pero… si no queda otra, veamos qué se puede hacer.

    Cómo instalar dependencias si tenemos acceso ssh

    Existen algunos hostings compartidos que permiten algún tipo de acceso vía ssh (o similar).

    Si este es el caso, puede que hasta tengamos composer ya instalado… claro que la versión que podremos encontrar puede ser algo añeja y, como el servidor lo administra otro, tendremos que poner un ticket de soporte y rogar para que se actualice.

    Si tenemos suerte, podremos ejecutar composer y actualizar nuestras dependencias directamente en el servidor como si estuviéramos en un ambiente controlado por nosotros mismos… si esto no funciona podemos intentar la sigueinte opción.

    Cómo instalar dependencias en un hosting compartido si no tenemos acceso ssh

    Este es ciertamente el caso más común: no hay posibilidad de hacer ssh al servidor.

    Claro que, de alguna forma, nuestro código debe llegar al hosting… para eso lo contratamos a fin de cuentas, ¿cierto?

    Pues en este escenario lo que podemos hacer es preparar lo que vamos a subir al servidor en un directorio especial y luego subirlo.

    La idea de hacer esto en lugar de subir directamente lo que tenemos en nuestro directorio de trabajo es:

    1. Minimizar la transferencia (para disminuir el tiempo que insumirá la subida)
    2. Minimizar la cantidad de espacio que ocuparemos en nuestro espacio de hosting
    3. Evitar subir información sensible que sólo debería estar disponible en nuestro entorno de desarrolllo

    Cómo preparar la subida

    Idealmente nuestro código estará versionado (Usando git, svn o similar).

    Si este es el caso, lo que podemos hacer es generar un nuevo directorio local mediante un clone o checkout (depende de cuál sea el controlador de versiones que usemos).

    Una vez que tengamos el código descargado será tiempo de instalar las dependencias.

    Lo ideal sería subir siempre una copia limpia de nuestro sistema. Si esto no es posible, al menos deberíamos eliminar los contenidos del directorio vendor de nuestra aplicación para no acumular dependencias que ya no sean necesarias.

    Cómo actualizar las dependencias usando composer

    Independientemente de cuál sea el lugar donde ejecutaremos composer, el comando que debemos ejecutar es:

    composer install --no-dev -o

    Con este comando sólo se instalar las dependencias necesarias en producción y se optimizará el autoloading (dando mejor performance a nuestra aplicación).

  • Qué se necesita para desarrollar usando Symfony en Windows

    Qué se necesita para desarrollar usando Symfony en Windows

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

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

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

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

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

    Instalando PHP en Windows

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

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

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

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

    Deberías ver algo como:

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

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

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

    Instalando composer en Windows

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

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

    Selección del ejecutable de PHP

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

    Composer desde la terminal de Windows

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

    Instalando git en Windows

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

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

    Habilitando extensiones de php necesarias para Symfony

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

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

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

    Contenido original del archivo php.ini

    Debe quedar así:

    Creando tu primer proyecto Symfony en Windows

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

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

    ¡Está todo listo!

    Ahora sólo te queda empezar a codear 🙂

    Adelante!

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

  • Cómo trabajar con grandes archivos JSON usando PHP

    Cómo trabajar con grandes archivos JSON usando PHP

    Recorrer un archivo json es algo sencillo, ¿cierto? Pues parece que cuando el tamaño del archivo es grande las cosas ya no son tan simples…

    Lo que se haría típicamente sería algo similar a:

    <?php
    
    $json = json_decode( file_get_contents( $argv[1] ), true );
    
    print_r( $json );

    El problema aquí es que se requiere primero leer todo el archivo para, recién entonces empezar a procesar…

    Tomemos un ejemplo como el árbol de categorías de MercadoLibre Brasil. Este archivo pesa 229 MB (Una vez descomprimido).

    229 MB de puro json… es bastante, sí, pero… ¿es mucho?

    La respuesta en definitiva depende la configuración que tengas en tu php.ini.

    A efectos de este análisis hice un cambio al mío de modo que quede así:

    ; Maximum amount of memory a script may consume (128MB)
    ; http://php.net/memory-limit
    memory_limit = 128

    128 MB no es algo inusual para una instalación de PHP (Al día de hoy, es el valor por defecto), con lo cual, estaremos en un problema al tratar de leer un archivo de más de 200…

    Aumentar la memoria disponible

    Una solución posible es darle más holgura a nuestro intérprete (En este caso, dejarle ocupar más memoria).

    Podemos hacerlo cambiando la configuración a algo como:

    ; Maximum amount of memory a script may consume (128MB)
    ; http://php.net/memory-limit
    memory_limit = 256

    Pero eso es, como decimos en mi país, «Pan para hoy, hambre para mañana».

    Si elegimos esta ruta (y queremos asegurarnos de estar bien cubiertos), lo ideal sería hacer esto:

    ; Maximum amount of memory a script may consume (128MB)
    ; http://php.net/memory-limit
    memory_limit = -1

    Y ahí el limitante que tendremos será la memoria física de la computadora… algo drástico para mi gusto y, más aún, poco practicable.

    El problema es que no siempre tenemos acceso a la configuración de php (como por ejemplo, en un hosting compartido).

    Aún si lo tuviéramos, tengo dudas sobre la posibilidad de alterar un setting tan sensible como este, ya que al aprovecharnos de los recursos de una forma tan poco medida estaríamos muy probablemente afectando a nuestros vecinos.

    Mejorar el modo de procesar JSON

    Otra forma de resolver el problema es cambiar la librería que usamos para procesar JSON.

    Existen varias alternativas pero en este artículo quiero mostrarte JsonReader (Una extensión de PHP).

    Una vez instalada, es bastante fácil de usar. Veamos un ejemplo de cómo obtener todos los nombres de los artículos del archivo que te mostraba antes:

    <?php
    
    require 'vendor/autoload.php';
    
    use pcrov\JsonReader\JsonReader;
    
    $reader = new JsonReader();
    $reader->open( $argv[1] );
    
    while ($reader->read("name")) {
        echo $reader->value(), "\n";
    }
    $reader->close();

    Al usar el comando

    php process.php categoriesMLB 

    Obtenemos una salida como:

    Acessórios de Carros
    Acessórios para Veículos
    Acessórios de Carros
    Exterior
    Interior
    Comércio
    Agro, Indústria e Comércio
    Comércio
    Contêineres Marítimos
    Insumos
    Máquinas
    Móveis
    Outros
    Acessórios para Veículos
    Acessórios para Veículos
    Acessórios de Carros
    Acessórios de Motos
    Acessórios Náutica
    Ferramentas
    GPS
    Limpeza Automotiva

    Que continúa por unas 1218885 líneas

    Reducir el problema

    Por último, no podemos dejar de lado la solución de «pensamiento lateral» (Que suele ser la mejor :)): re-definir el problema en términos más simples.

    Se trata de hacernos la pregunta: ¿Realmente necesitamos ese archivo? ¿No podríamos trabajar con uno más pequeño?

    Claramente, la respuesta dependerá del contexto más que de cuestiones de tecnología, pero no estaría completo el artículo si no lo mencionara.

    (Si preferís resolver el problema intentando reducir el tamaño del archivo, este artículo puede ser de tu interés).

    Algunas notas finales

    Es importante distinguir entre la necesidad de procesar un archivo tan grande en forma online (Es decir, mientras estamos generando una respuesta para un visitante al sitio) y offline (En un cronjob en la mitad de la noche por ejemplo).

    Claramente, esta herramienta va bien para el segundo caso (Para el primero te diría que si estás con esa necesidad tendrías que re-pensar algo…).

    Por último, no puedo dejar de agradecer a mis amigos y colegas Alejandro Chapiro y Eric Danan por la inspiración para escribir este texto (Y la discusión sobre soluciones que te mostré acá).

  • Cómo prevenir la subida duplicada

    Cómo prevenir la subida duplicada

    Ultimamente vengo trabajando bastante con procesamiento de planillas Excel usando PHP.

    Por lo general, el workflow del usuario es algo así como:

    • Trabajar con algún otro sistema (HomeBanking, Plataforma de trading, etc…)
    • Descargar información en formato Excel
    • Importar planilla descargada al sistema que yo desarrollé
    • Trabajar la información dentro del sistema

    Uno de los errores comunes cuando una persona carga información a un sistema es el de la carga duplicada.

    Este problema se agrava cuando los duplicados no siempre son errores .

    Esta condición hace que no sea simple detectar y prevenir la importación duplicada.

    El escenario sería algo como tomar el workflow original y modificarlo ligeramente:

    • Trabajar con algún otro sistema (HomeBanking, Plataforma de trading, etc…)
    • Descargar información en formato Excel
    • Importar planilla descargada al sistema que yo desarrollé
    • Salir por un café
    • Olvidar qué fue lo último que se hizo
    • Volver a importar planilla descargada al sistema que yo desarrollé
    • Trabajar la información dentro del sistema

    Siendo que el sistema no puede automáticamente eliminar un registro sólo porque su contenido ya exista parece poco lo que puede hacerse…

    Mi idea de solución se basa en intentar detectar archivos que ya han sido subidos al sistema de un modo eficiente.

    Disclaimer: la solución que voy a descibir aquí es, hasta el momento, también teórica (más allá de alguna prueba de concepto).

    Cómo detectar archivos ya subidos

    Una primera idea es:

    1. Almacenar todos los archivos subidos
    2. Realizar una comparación binaria de los contenidos del nuevo archivo subido contra todos los anteriores

    No me suena muy eficiente que digamos :p

    Una idea algo más elaborada es la de usar un hash como base de comparación.

    La idea es por cada archivo subido calcularlo y comparar contra los ya existentes.

    Lo interesante de este método es que el cálculo es rápido y la comparación es casi trivial.

    Un punto a tener en cuenta sin embargo es que, aunque muy poco probable, existe el riesgo de «falsos positivos».

    Dos strings diferentes pueden tener el mismo hash, con lo cual el sistema podría dar por duplicado dos archivos que no tengan nada que ver.

    Para evitar este problema dejaré la solución 1 como fallback para los casos en que encuentre hashes duplicados.

    Implementación en PHP de detección de subidas duplicados

    No te voy a dejar sólo con palabras, ¿cuál sería la gracia, cierto?

    Vamos a ver algo de código:

    Voy a almacenar todo el contenido de los archivos en una base de datos.

    A efectos de esta prueba me basta con SQLite, pero podés usar la que te resulte más cómoda (o guardar los archivos en algún directorio si preferís).

    Entonces, lo primero es crear la base usando este SQL:

    CREATE TABLE "files" (
    	`id`	INTEGER PRIMARY KEY AUTOINCREMENT,
    	`contents`	BLOB,
    	`hash`	TEXT
    );
    CREATE TABLE sqlite_sequence(name,seq);

    Luego tendremos dos archivos, el que permite subir el archivo (HTML) y el que recibe el archivo (PHP):

    upload.html

    <form action="get_file.php" method="post" enctype="multipart/form-data">
        <p>Importar: <input type="file" name="fileToUpload"/></p>
        <p><input type="submit" value="Subir"/></p>
    </form>

    get_file.php

    <?php
    /**
    * Created by PhpStorm.
    * User: mauro
    * Date: 1/9/19
    * Time: 12:46 PM
    */

    $tmp_name = $_FILES["fileToUpload"]['tmp_name'];
    $hash = sha1_file($tmp_name);

    echo 'Hash for uploaded file: "' . $hash . '"<br/>';

    $new_file_contents = file_get_contents($tmp_name);

    $db = new PDO('sqlite:uploded.sq3');

    if ( $suspects = find_file_by_hash($hash) ) {
    foreach ( $suspects as $suspect ) {
    if ( $suspect == $new_file_contents) {
    die ('Duplicate file');
    }
    }
    }

    if ( save_file_record( $new_file_contents, $hash ) ) {
    echo 'File saved ok!';
    } else {
    echo 'Saving error :(';
    }

    /**
    * @param $hash
    * @return array
    */
    function find_file_by_hash( string $hash ) : array
    {
    GLOBAL $db;

    $sql = "SELECT * FROM files WHERE hash = '$hash'";

    if ( $st = $db->query( $sql ) ) {

    return array_map( function( $r ) {

    return $r['contents'];
    }, $st->fetchAll( PDO::FETCH_ASSOC ) );
    } else {

    return [];
    }
    }

    /**
    * @param string $contents
    * @param string $hash
    * @return int
    */
    function save_file_record( string $contents, string $hash ) : bool
    {
    GLOBAL $db;

    $sql = "INSERT INTO files ( contents, hash ) VALUES ( :contents, :hash )";

    $st = $db->prepare( $sql );
    $st->bindParam('contents', $contents, PDO::PARAM_LOB );
    $st->bindParam('hash', $hash );

    if ( !$st->execute() ) {
    print_r( $st->errorInfo() );

    return false;
    }

    return true;
    }

    Para hacer correr este código sólo hay que grabar los archivos en un mismo directorio e iniciar el servidor web que viene con PHP:

    php -S localhost:8080

    Y subir un archivo varias veces. La primera deberías ver el mensaje «File saved ok!» y la segunda «Duplicate file».

    Listo! No te recomiendo implementar el código así como está, pero espero que haya servido para explicar la idea :).

    Si preferís una versión en video, acá hay un link a YouTube.