Categoría: Cómo hacer para…

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

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