Categoría: Cómo hacer para…

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

  • Cómo se usan los callbacks en PHP

    Cómo se usan los callbacks en PHP

    Este es uno de mis temas preferidos cuando a PHP se refiere.

    Debo confesar que lo descubrí bastante tarde, pero fue prácticamente amor a primera vista.

    Veamos un ejemplo:

    Si tengo un array con números ( [ 1, 2, 3, 4, 5 ] ) y quiero obtener sólo los números pares, la forma normal de hacerlo sería algo como:

    $pares = [];
    foreach ( $numeros as $numero ) {
       if ( $numero % 2 == 0 ) {
           $pares[] = $numero;
       }
    }

    Levantemos un poco el nivel de abstracción:

    function esPar( $numero )
    {
        return $numero % 2 == 0;
    }
    
    $pares = [];
    foreach ( $numeros as $numero ) {
       if ( esPar( $numero ) ) {
           $pares[] = $numero;
       }
    }

    Hasta acá la versión clásica.

    Ahora, si mirás nuevamente, podés ver que se está llamando a la función esPar() sobre cada uno de los elementos del array $numeros y nos estamos quedando sólo con aquellos que lo son, o, dicho de otro modo, estamos dejando afuera aquellos elementos que no son pares. A esta operación se la conoce como filtro.

    Ahora bien, es claro que esPar no es el único criterio por el que podríamos querer filtrar un array.

    Si tomamos un ejemplo donde sólo queremos quedarnos con los elementos mayores que tres:

    function esMayor3( $numero )
    {
        return $numero > 3;
    }
    
    $mayores = [];
    
    foreach ( $numeros as $numero ) {
       if ( esMayor3( $numero ) ) {
           $mayores[] = $numero;
       }
    }

    La estructura de estos dos scripts es muy similar:

    • Se parte de un array
    • Se aplica una función booleana a cada elemento
    • Se retornan sólo los elementos para los cuales la función evalúa en true

    Notá que la única diferencia entre los dos scripts es cuál es la condición que determina si el elemento queda adentro del array de resultado o no

    Y acá es donde aparece este genial concepto tomado de la programación funcional: la posibilidad de que las funciones puedan ser argumentos de otras funciones.

    Si nunca viste este concepto antes, te sugiero que te tomes un par de segundos para digerir lo que acabás de leer.

    ¿Ya está? Ok, sigamos.

    Lo que estoy diciendo es que podrías hacer una función genérica que tome las partes en común de ambas situaciones y reciba como parámetro el criterio de filtro que se debe usar.

    Se vería algo como:

    function filtrar( array $array, $f )
    {
       $res = [];
    
       foreach ( $array as $elemento ) {
          if ( $f( $elemento ) ) {
             $res[] = $elemento;
          }
       }
    
       return $res;
    }

    Notá como $f es a la vez una variable y una función.

    A mi esto me recuerda a la escena de Matrix donde el chico monje dobla la cuchara (Si no viste la peli no tiene mucho sentido el comentario, pero si la viste seguro entenderás de lo que hablo)

    Antes de seguir te cuento (por si no lo adivinaste aún) que esta función ya existe en PHP: array_filter.

    La tomé como ejemplo (existen muchas funciones nativas que usan callbacks) porque me parece de lo más claro para explicar el concepto para quien nunca lo vio, pero las posibilidades son realmente muchas.

    La pregunta que puede surgirte es: ¿por qué no puedo seguir escribiendo esto como lo hacía hasta ahora? ¿Qué me aporta conocer los callbacks?

    La respuesta es que sí, no hay nada que puedas lograr (desde el punto de vista de funcionalidad) que no pudieras lograr sin usar callbacks.

    La segunda pregunta es más interesante. Lo que te aporta usar este mecanismo es lo mismo que cualquier otro mecanismo de abstracción (como las funciones con parámetros por ejemplo) es la posibilidad de lograr el mismo resultado con menor esfuerzo (En este caso, escribiendo menos código, ya que tal vez te exija pensar un poquito más que de la otra forma).

    Un buen programador siempre está ávido de nuevas formas de conseguir esto.

    Menos código implica menos probabilidades de dejar errores molestos e indetectables, es decir, menos fines de semana y noches sin dormir arreglando el sistema.

    ¿Qué usos interesantes le ves a este mecanismo?

  • Cómo interactuar con Excel desde PHP

    Cómo interactuar con Excel desde PHP

    Un problema bastante común en el desarrollo de aplicaciones para empresas es la interacción con Excel (Levantar datos, generar planillas, etc…).

    Un modo sencillo de evadir el problema (y que muchas veces funciona), es exportar la información hacia archivos de texto separados por comas (Los famosos CSV) y luego tratarlos desde PHP con funciones como fgetcsv.

    Obviamente, esto servirá si se trata de una aplicación que sólo requiere leer datos… ¿Qué pasa si necesitamos generarlos? (O si la estructura de la planilla en cuestión es algo más compleja).

    Como en la mayoría de los casos, existen diversas opciones de solución. Una particularmente buena es la librería PHPExcel PHPSpreadSheet.

    Generar un archivo Excel desde PHP

    Un detalle importante de la librería PHPSpreadSheet es que permite interactuar con planillas de cálculo diversas (xls, xlsx, Gnumeric… ¡hasta CSV!), con lo cual, como podrás imaginar, al escribir (o leer) un archivo habrá que aclarar en qué formato se lo quiere.

    Veamos un ejemplo:

    <?php
    
    use PhpOffice\PhpSpreadsheet\IOFactory;
    use PhpOffice\PhpSpreadsheet\Spreadsheet;
    
    $spreadsheet = new Spreadsheet();
    
    $spreadsheet->setActiveSheetIndex(0)
        ->setCellValue('A1', 'Hola')
        ->setCellValue('B2', 'Mundo!')
        ;
    
    $writer = IOFactory::createWriter($spreadsheet, 'Xls');
    $writer->save('salida.xls');

    En este caso estamos usando dos clases (PhpOffice\PhpSpreadsheet\Spreadsheet y PhpOffice\PhpSpreadsheet\IOFactory).

    La primera es la planilla propiamente dicha, la segunda tiene el objetivo de crear un objeto capaz de escribir esa planilla al formato específico (En este caso, xls).

    Una vez obtenido el objeto PhpOffice\PhpSpreadsheet\Writer\Xls (Resultado de la llamada a IOFactory::createWriter) podemos usar su método save para efectivamente generar la planilla.

    Veamos otro ejemplo:

    <?php
    
    use PhpOffice\PhpSpreadsheet\IOFactory;
    use PhpOffice\PhpSpreadsheet\Spreadsheet;
    
    $spreadsheet = new Spreadsheet();
    
    $spreadsheet->setActiveSheetIndex(0)
        ->setCellValue('A1', 'Hola')
        ->setCellValue('B2', 'Mundo!')
        ;
    
    header('Content-Type: application/vnd.ms-excel');
    header('Content-Disposition: attachment;filename="salida.xls"');
    
    $writer = IOFactory::createWriter($spreadsheet, 'Xls');
    $writer->save('php://output');

    Aquí, en lugar de escribir el archivo salida.xlsen el servidor, lo estamos ofreciendo para que el cliente lo descargue (Algo muy útil si se trata de exportar un reporte desde nuestra aplicación).

    Un tercer ejemplo:

    <?php
    
    use PhpOffice\PhpSpreadsheet\IOFactory;
    use PhpOffice\PhpSpreadsheet\Spreadsheet;
    
    $spreadsheet = new Spreadsheet();
    
    $spreadsheet->setActiveSheetIndex(0)
        ->setCellValue('A1', 'Hola')
        ->setCellValue('B2', 'Mundo!')
        ;
    
    header('Content-Type: application/vnd.ms-excel');
    header('Content-Disposition: attachment;filename="salida.xlsx"');
    
    $writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
    $writer->save('php://output');

    Es muy sutil la diferencia, por si no lo notaste, el objeto writer que se creará será de tipo  PhpOffice\PhpSpreadsheet\Writer\Xlsx en lugar de  PhpOffice\PhpSpreadsheet\Writer\Xls.

    ¡Esa pequeña «x» hace una gran diferencia!

    Los formatos xls cayeron en desuso a partir de la versión 2003 de Excel, el xlsx es un formato diferente (basado en XML y comprimido), con lo cual, será muy diferente el resultado obtenido usando uno u otro writer… ¡Ojito! 😉

    Leer un archivo Excel usando PHP

    La lectura de un archivo Excel es bastante similar:

    <?php
    
    use PhpOffice\PhpSpreadsheet\IOFactory;
    
    $spreadsheet = IOFactory::load('entrada.xls');
    $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
    var_dump($sheetData);

    En este caso, la clase PhpOffice\PhpSpreadsheet\IOFactoryintentará «adivinar» el tipo de planilla de la que se trata (Algo bastante útil cuando tienes que tratar con diferentes tipos de planilla).

    ¿Cómo funciona esta adivinación? es bastante complejo… el punto es que puede fallar, con lo cual, si sabés exactamente el tipo de planilla que vas a usar, más vale usar un Reader específico:

    <?php
    
    use PhpOffice\PhpSpreadsheet\Reader\Xls;
    
    $reader = new Xls();
    $spreadsheet = $reader->load('entrada.xls');
    
    $sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
    var_dump($sheetData);
    

    Conclusión

    Como podrás ver en los ejemplos, es bastante sencillo realizar operaciones sobre una planilla Excel casi como si estuvieses escribiendo una macro.

    Además de lo dicho hasta aquí, la documentación de PHPSpreadSheet es un lujo (Sólo que está en Inglés).

  • Cómo obtener la cotización del día de una acción con PHP

    Todo lo que voy a mostrarte acá se basa en la API de Yahoo Finance.

    Lo primero que tenés que hacer es instalar composer.

    Segundo, inicializar el proyecto:

    php composer.phar init

    Tercero: agregar la dependencia del paquete https://github.com/scheb/yahoo-finance-api:

    php composer.phar require scheb/yahoo-finance-api

    Y después podés usar un código como este:

    #!/usr/bin/php
    <?php
    
    require __DIR__ . '/vendor/autoload.php';
    
    $client = new \Scheb\YahooFinanceApi\ApiClient();
    
    $d = new DateTime($argv[2]);
    
    echo "Buscando ".$argv[1]." en fecha: ".$d->format('d/m/y').PHP_EOL;
    try {
      $data = $client->getHistoricalData($argv[1], $d, $d);
    
      echo $data['query']['results']['quote']['Close'].PHP_EOL;
    } catch ( Exception $e ) {
      echo $e->getMessage().PHP_EOL;
    }
    

    En este ejemplo, lo que tenés es una utilidad de línea de comandos que recibe dos parámetros:

    1. El ticker (Símbolo del papel en cuestión, por ejemplo TS para la acción de Tenaris)
    2. La fecha.

    La llamada sería así:

    php get_stock_price.php TS Yesterday

    y el resultado será algo como:

    Buscando TS en fecha: 15/12/16
    34.220001
    
  • Webscrapping con PHP

    Webscrapping con PHP

    Me encontré recientemente con este problema desarrollando un sistema para un cliente y creo (¡y espero!) que mi experiencia pueda ayudar a otros.

    El desafío era el siguiente: nuestro cliente es una empresa que se dedica a la administración de activos financieros. Como parte de su operatoria, requieren la consolidación de información que actualmente está dispersa en una serie de planillas Excel.

    Parte de esa información se refiere a movimientos de acciones y bonos. Una de las tareas que se realizaba manualmente era el cálculo de cuánto dinero se había movido al realizar una compra o venta de alguno de estos instrumentos (Simple: cantidad de títulos por precio del título al día de la transacción).

    La complejidad de este cálculo reside en cómo conseguir el precio que tenía el activo al día en que se realizó la transacción. Antes de la intervención de Leeway esta tarea estaba a cargo de una empleada de la compañía (Entre muchas otras, tenía que buscar en el sitio de Yahoo Finance u otro similar y completar ese dato).

    Desarrollamos una aplicación que fuera capaz de consultar esa información y realizar ese cálculo en forma automatizada.

    No es realmente complicado hacerlo cuando se cuenta con una API bien hecha y documentada (Incluso mejor si tenemos a mano un SDK para PHP).

    Lo complicado del tema fue sacar la información de bonos, para la cual no encontramos ninguna fuente pública que tuviera buena información (¿Conocés alguna?, te invito a que dejes un comentario 🙂 ), con lo cual… no quedó opción más que arremangarse y hacer algo de web scrapping (Todo sea por ahorrarle unas horas de rastreo todos los meses a un cliente).

    Así que ahí nos metimos, cURL y SimpleHTMLDom en mano, a remover la maleza y a ver qué encontrábamos.

    Y la verdad… lo que encontramos no fue nada bonito. La primera misión fue entender todo el camino que un usuario humano tenía que recorrer para llegar a la información que nosotros queríamos obtener.

    Una vez que tuvimos esa información, nuestro primer intento fue apuntarle a la última URL con un simple GET y escarbar el resultado… no señor, ¿tan fácil iba a ser el tema?

    Lo primero que notamos era que la URL final tenía poco en común con la inicial (y sin acceso a la base de datos que andaba por detrás… difícil hacer la conversión…). Bien, retrocedamos dos casilleros.

    Nuevamente, bajemos el contenido de la primera URL y busquemos el link que necesitamos (¡Gracias S.C. Chen y compañía por ponerle sintaxis tipo jQuery al SimpleHTMLDom!).

    ¡Primera prueba superada! Tenemos una nueva página que está un pasito más cerca. Veámosla un poco más de cerca… ¡ahá! ¡Se trata de un formulario que va por POST! Ningún problema, nuestro amigo FireBug nos mostrará el camino. Ah, pero esto es muy simple… una pequeña llamada Ajax y voilà.

    Otra vez… ¡no tan rápido! Faltan los parámetros invisibles que se generan del lado del servidor… jejeje (Léase con risa de programador malicioso). Ok, volvamos a revisar ese HTML. Bien, acá están esos input hidden, no hay problema, los agregamos y listo.

    ¿Cómo que 404? ¡Si estoy viendo la información! ¿qué te pasa cURL?

    ¿Cómo? ¿Que el valor de un campo no es un literal si no una expresión?… qué ganas de complicarle la vida al prójimo… bueno… usemos el eval de php. Esta parte sí te la puedo mostrar:

    foreach ( $dom->find('.ajax-token') as $token ) {
      if ( $token->attr['name'] == '__atcrv' ) {
        $atcrv = eval('return '.$token->attr['value'].';');
      }
    }<div class="open_grepper_editor" title="Edit &amp; Save To Grepper">

    Y ahora, ¿qué otra sorpresita hay por ahí? ¿La respuesta (si da 200) viene gzipeada? 0 problema: gzdecode se encarga y por fín, tenemos a mano la tablita con los precios del bono buscado para el día buscado. Muchas gracias, buenas noches.


    La historia fue para poner en contexto, pasando en limpio (para generalizar un poco también):

    1. Todo lo que un navegador hace, cURL puede hacerlo también (Iba a decir, todo lo que una persona hace, pero después me vino a la mente el reCaptcha).
    2. Este mecanismo no es ni de lejos ideal. Basta con un pequeño cambio en el maquetado del sitio para que todo se rompa (Pero bueno… si no disponemos de una buena API, no creo que haya otro mucho mejor)
    3. No hay que temerle a un poco de ingeniería inversa 🙂
    4. La solapa Net de FireBug da un montón de información súper útil.

    ¿Me olvidé de algo importante?