La primera pregunta que uno se haría ante esta situación es: ¿para qué molestarse? 🙂
Es decir, cualquiera preferiría un sitio rápido antes que uno lento, ¿cierto?
Pero… ¿para qué sirve realmente tener un sitio más rápido?
Bueno pues hay dos respuestas inmediatas:
Mejor experiencia de usuario
Mejor posicionamiento orgánico
Ambas redundan en beneficios muy palpables para los dueños de los sitios: clientes más satisfechos y mayor afluencia de potenciales clientes.
Qué factores influyen
Muy bien, ahora que estamos de acuerdo en que tener un sitio rápido es beneficioso tenemos que comprender qué factores influyen en la velocidad de carga.
La realidad es que son muchos y, lamentablemente, unos cuantos están fuera de nuestro control, pero igualmente analicémoslos un momento.
Toda la interacción con un servidor web se basa en:
Una traducción de un nombre de dominio en una dirección IP
El establecimiento de una conexión entre el cliente y el servidor
El envío de información desde el cliente hacia el servidor
La resolución del pedido por parte del servidor
El envío de información desde el servidor al cliente
El rendering del lado del cliente
Como puede verse, un factor decisivo será la velocidad de la red subyacente en esta comunicación (Lo que se conoce como ancho de banda) pero definitivamente no es el único.
De modo que existen medidas que pueden tomarse para mejorar el tiempo del lado del cliente (frontend) y otras tantas del lado del servidor (backend).
Cómo acelerar el frontend de una aplicación Web
Acelerar el frontend implica minimizar:
La cantidad de pedidos al servidor
La cantidad de información intercambiada en cada pedido
El tiempo que insume el rendering
Un típico cuello de botella lo encontramos en el tamaño de las imágenes u otros archivos estáticos (JavaScript, CSS, etc…).
Reduciendo estos es posible ganar mucho.
Una herramienta muy útil para medir estos cuellos de botella es GTMetrix
Cómo acelerar el backend de una aplicación Web
Y luego está la otra cara de la moneda: el servidor.
¿Qué podemos hacer para que nuestro servidor responda más rápido?
Nuevamente, tenemos que saber a dónde apuntar nuestros cañones.
Usualmente una aplicación web consiste en:
Un webserver
Una base de datos
Algún script
En nuestro caso asumiremos que se trata de PHP, aunque los principios aplican para cualquier lenguaje.
Es muy probable que la parte más compleja de optimizar sea el código de la aplicación.
Principalmente porque lograr el mismo resultado usando mejor código requiere mucho análisis y, sobre todo, mucho testing para asegurarnos de no romper nada en el camino.
Hay unas cuantas mejoras que pueden hacerse desde la infraestructura que son muy simples y aportan mucho. Por ejemplo:
Y luego hay otras herramientas que complementan al servidor web como ser los cachés de memoria (APC, Memcached, Redis, etc…)
También vale la pena investigar qué consultas pueden estar trabando la base de datos, por ejemplo usando el MySQL slow query log e intentar mejorarlas.
Y por último, si sospechamos que el código está haciendo de las suyas podemos usar un profiler como XDebug para orientarnos sobre dónde poner la lupa.
Conclusión
En definitiva, acelerar un sitio web lento es perfectamente posible pero también bastante laborioso.
Siempre debe primar un criterio de costo/beneficio para saber cuando se ha alcanzado una velocidad aceptable y ya no vale la pena el esfuerzo de seguir mejorándolo.
Muchos clientes se acercan a cualquier desarrollador con la idea de agregar a su sitio un «carrito de compras» pero, cuando indagamos un poco más vemos que el tema no es tan simple.
Por ejemplo: ¿sirve de algo el carrito de compras sin la posisbilidad de realizar el pago al final?
Pero bueno, para no hacer un post enorme, comencemos por la parte del carrito propiamente dicho y dejemos el tema de los pagos para otro.
Qué puede hacerse con un carrito de compras
El carrito de compras es un espacio donde un visitante puede llevar registro de los productos que desea comprar.
Desde el punto de vista técnico/funcional, debe ser posible:
Ingresar productos
Quitar productos
Ver los productos existentes
Confirmar la compra
Para poder ingresar productos al carrito será necesario visualizar cuáles son los productos disponibles en la tienda.
Qué se necesita para armar un carrito de compras
La implementación de todo el mecanismo de administración del carrito requerirá de:
Algún tipo de base de datos que contenga el catálogo de productos
Una aplicación que permita al visitante:
Ver ese catálogo
Ver los detalles de cada producto
Agregar productos a su carrito
Ver los contenidos de su carrito
Eliminar productos del carrito
Confirmar su compra
Una pasarela de pagos para completar la transacción
Por simplicidad imaginemos que tenemos una primera página tipo catalog.php:
Estoy dejando de lado unos cuantos detalles para no complicar el ejemplo: como ser categorías de productos, cantidad a agregar en cada interacción, etc…
Continuemos por el archivo add_to_cart.php.
Aquí es donde reside la clave de la cuestión: el carrito debe acompañar al visitante durante toda su estadía en el sitio.
Precisamente, la idea del carrito es permitir que la compra se vaya armando conforme el usuario va descubriendo los productos.
De modo que necesitaremos un medio de almacenamiento que persista durante la navegación.
A estos efectos php pone a nuestra disposición el arreglo $_SESSION.
Es ahí donde vamos a almacenar los productos seleccionados por el visitante:
De este modo guardamos los ids de los productos seleccionados y su cantidad.
No es necesario guardar más que los ids de producto ya que con eso será suficiente para recuperar toda la información a la hora de mostrar el carrito o confirmar la compra.
Queda por completar los archivos de ver el detalle del producto y remover del carrito pero creo que con lo que viste hasta aquí no deberías tener problemas para armarlos.
Un detalle que haría más amena la experiencia para el usuario sería utilizar AJAX para meter y sacar productos del carrito.
Y, por supuesto, deberás tener un enlace para realizar el pago.
Es una necesidad bastante usual la de recoger información disponible en Internet.
Algunos sitios permiten hacerlo en forma amigable exponiendo algún tipo de API, que puede ser consumida conectándose a un webservice.
En otros casos, lo mejor que puede hacerse es algo de WebScrapping.
Claro que es una técnica muy poco fiable y bastante costosa en términos computacionales, pero… si no queda otra…
En este caso, de lo que se trata es de extraer las direcciones de correo electrónico presentes en una página cualquiera, por ejemplo:
Se trata de un proceso de dos pasos
Cómo obtener los contenidos de una página usando PHP
El primer paso es obtener el contenido de la página. En realidad, lo que nos interesa es el HTML de la página, las imágenes, hojas de estilo y demás no es necesario en este momento.
Una forma muy sencilla de conseguirlo es usar la función file_get_contents:
Cómo identificar correos electrónicos dentro de un texto
El segundo problema a resolver es, una vez que obtuvimos todo ese texto HTML… ¿cómo podemos determinar qué direcciones de correo electrónico hay dentro?
Afortunadamente, las direcciones de correo electrónico siguen un patrón bastante estructurado: todas tienen una @ en el medio y, al menos, un . a la derecha.
Podríamos usar una expresión mucho más compleja, pero comencemos por esta.
Nuestro aliado en esta ocasión será la función preg_match_all:
Un cliente con el que trabajé estaba buscando aumentar el tamaño de su base de datos para realizar mailings y me pidió que le diseñe un robotito para extraer las direcciones de correo que estén presentes en las páginas resultantes de ciertas búsquedas de Google.
Si bien personalmente no lo considero algo muy productivo (Discusión aparte sobre la efectividad/ética de enviar correo no deseado o si realmente se trata de correo no deseado cuando se ofrece una solución que realmente va a ayudar a quien lo reciba), me pareció interesante el desafío técnico (y también, hay que reconocerlo, a veces simplemente hay que darle al cliente lo que quiere :)).
Lo primero que se me ocurrió fue que, así como hay APIs para entrar a Gmail, a GoogleDocs y demás, debía haber alguna para usar el motor de búsqueda y obtener los datos en formato JSON, XML o algún otro formato digno de un webservice.
Y ahí vino mi primera sorpresa (Eso es lo que me fascina de la programación: siempre hay cosas nuevas para aprender :)): a Google no le gusta que los robots le usen su motor (Irónico, ¿no? ellos viven de scrappear toda la web, pero…).
De modo que, para lograr esta hazaña había que arremangarse y parsear HTML (Algo como lo que te comenté sobre cómo acceder a sitios que no te dan API) o bien, siendo que se trata de un sitio archi-conocido, buscar en Packagist.org que seguro iba a tener algo piola para arrancar al menos.
Como de costumbre, Packagist no me defraudó y conocí el proyecto SERPS.
Qué es el proyecto SERPS
El proyecto SERPS es un esfuerzo por crear un conjunto de herramientas que permitan suplir esta falta, es decir, es un set de bibliotecas que permite (o, mejor dicho, permitirá) automatizar las búsquedas en diferentes motores (Actualmente sólo Google está implementado).
Es bastante interesante desde su arquitectura, bien modular y de bajo acoplamiento (Se nota la aplicación del patrón strategy).
A nivel práctico, se trata de una capa de abstracción muy útil para «olvidarnos» de la complejidad de interpretar los resultados de una búsqueda (¡y los caprichos de los pedidos!).
Preparando el proyecto para usar SERPS
SERP está diseñado para ser usado con composer. Al principio de su documentación hay un ejemplo de archivo composer.json que te recomiendo copiar y pegar (Yo intenté incluir las dependencias que iba necesitando y no funcionó nada :p):
Con esta configuración (y, obviamente, un composer install) tenés todo lo necesario para hacer queries a Google usando PHP.
Cómo hacer una búsqueda en Google usando SERPS
Como decía al comienzo, a Google no le gustan los robots, así que… hay que hacer de cuenta que la búsqueda la hace una persona. Obviamente, una persona que está usando algún navegador. Todo esto se traduce en usar un User-Agent adecuado.
Te muestro un ejemplo de lo que yo hice:
#!/usr/bin/php
<?php
use Serps\HttpClient\CurlClient;
use Serps\Core\Browser\Browser;
use Serps\SearchEngine\Google\GoogleClient;
use Serps\SearchEngine\Google\GoogleUrl;
require_once 'vendor/autoload.php';
$userAgent = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36";
$browserLanguage = "es-AR";
$browser = new Browser(new CurlClient(), $userAgent, $browserLanguage);
$googleClient = new GoogleClient($browser);
$googleUrl = new GoogleUrl();
foreach ( file($argv[1]) as $searchTerms ) {
$googleUrl->setSearchTerm($searchTerms);
$response = $googleClient->query($googleUrl);
$results = $response->getNaturalResults();
foreach ($results as $result) {
if ( $result->url ) {
echo $result->url . PHP_EOL;
}
}
}
En mi caso, el script toma como parámetro un archivo (que asumo tiene una línea por búsqueda que se quiere hacer) y emite los links encontrados.
Fijate que sencillo:
Se crea un objeto Serps\SearchEngine\Google\GoogleClient usando un objeto Serps\Core\Browser\Browsercomo interface para realizar la comunicación HTTP y otro objeto Serps\SearchEngine\Google\GoogleUrlpara armar la consulta como es debido.
Luego de consultar se obtienen los resultados naturales (Es decir, aquellos resultados no pagos) y, por cada uno se emite el link. En mi caso todo esto va acompañado de otro script que busca direcciones de correo electrónico dentro de una página web y emite los resultados como para ser incorporados a un archivo .csv.
Ejemplo de uso del script
Ya teniendo las dos herramientas (la que busca links en Google y la que busca direcciones de mail dentro de una página), es muy fácil componerlas (Si usás algún sistema operativo POSIX como Linux al menos :p):
./get_links.php terms.csv | ./get_emails.php
En el archivo terms.csv se escribe, línea por línea, las búsquedas que quiero realizar, el script get_links.php emite cada resultado que obtiene y get_emails.php toma sus datos de la entrada estándar… justo lo que se necesita para usar pipes :).
Y claro, la frutilla del postre es guardar la salida de get_emails.php en un nuevo archivo .csv:
La tarea de hacer webscrapping no es muy grata que digamos (Especialmente porque los grandes esfuerzos que se hacen pueden resultar en vano si el sitio a ser scrappeado cambia su layout…), pero… a veces resulta útil.
Y, si bien algunos sitios hacen lo posible para «protegerse» de este tipo de prácticas… es poco lo que pueden hacer para evitarlas al 100%…
La pregunta que dió origen a este post era un poco más amplia:
Pero como ya hablé de cómo consumir webservices (Sean REST o SOAP) me voy a concentrar en la parte que me llamó la atención: cómo pasar de JSON a CSV.
Aclaremos los tantos antes de ir a los detalles:
Qué es JSON
JSON significa JavaScript Object Notation, es decir: notación de objetos de JavaScript. Está más allá del alcance de este artículo (y de este blog en general) hablar de las bondades (o falta de ellas) de JavaScript… hay mucho material muy bueno al respecto.
El punto es que, más allá de lo que a vos te guste o no, JavaScript tiene una sintaxis muy práctica para describir los objetos: todo lo que esté encerrado entre {} es un objeto (Con la excepción tal vez del cuerpo de las funciones…).
Esto lo hace un formato sumamente conveniente para el intercambio de información de estructuras complejas, ya que constituye un modo muy simple de pasar de un objeto en memoria a una representación textual (que puede ser enviada a través de un protocolo de intercambio de textos… por ejemplo HTTP :)).
Se ve muy simple (¡y lo es!) pero tiene un pequeño inconveniente: su sintaxis es muy poco permisiva (Dejá una , suelta y agarrate :p), por lo tanto, es muy conveniente usar las funciones propias de PHP para manipularlo (json_encode y json_decode).
Y acá me da un poco de nostalgia hablar del primer post que escribí para este blog, pero pienso que puede aportar a la respuesta saber cómo iterar sobre un JSON usando PHP.
Qué es CSV
CSV significa Comma Separated Values (Valores separados por comas). Se trata de un formato de texto que también es utilizado para el intercambio de información entre sistemas.
Usualmente el contenido de este tipo de archivos es una sucesión de filas que corresponden con registros cuya cantidad de campos puede variar de uno a otro y la longitud de cada campo también varía.
Una primera salvedad que hay que hacer es que JSON es un formato que permite estructuras complejas (de múltiples niveles por ejemplo), mientras que CSV es un formato mucho más simple (Se basa en estructuras «planas»), con lo cual, algo se perderá en el camino (o habrá que hacer alguna suposición o transformación de la información).
A efectos prácticos, asumamos que el JSON de partida es de un único nivel, algo como:
Ahora, si la necesidad es más compleja… nada mejor que buscar un poco :).
Buscando buscando encontré esta herramienta: https://github.com/danmandle/JSON2CSV (No la probé pero parece interesante… ¿te animás a probarla y comentar?)
Este post está inspirado en un caso muy interesante que me tocó resolver para un cliente.
Un poco de contexto para que se entienda de dónde viene el tema:
Una buena parte de los clientes de mi cliente llegan a través de correos que se reciben en info@...., claramente, te imaginarás que, dentro de los muchos que llegan, una parte es SPAM y la otra son contactos genuinos.
Separar la paja del trigo sería un desafío realmente interesante (Que probablemente involucraría algo de procesamiento del lenguaje natural, IA y esas cosas tan divertidas), pero… por el momento el presupuesto dio sólo para mejorar un poco el proceso de tratamiento del trigo una vez haya sido debidamente identificado.
Muy bien, entonces, el punto era que se estaba queriendo, además de re-enviar los correos útiles al equipo de ventas, subirlos de inmediato al sistema de newsletters de la empresa (Obviamente, se trataba de MailChimp).
Decidimos entonces crear una cuenta de GMail a la cual la persona encargada de identificar los correos de potenciales clientes pudiera re-enviarlos (además de al equipo de ventas) y, a partir de ahí, tener un robot que los procesara e incorporara la lista de mailing.
Técnicamente se trata de cuatro problemas:
Cómo autorizar el acceso a GMail vía API
Cómo ingresar a GMail y descargar los correos
Cómo procesar los datos descargados para extraer las direcciones de correo
Cómo incorporar esos datos a MailChimp
Sobre el último de los problemas escribí acá, así que concentrémonos en los tres primeros:null
Cómo configurar el acceso a GMail vía API
Hay una primera parte que hace a la autorización de la aplicación (Muy parecido a lo que hicimos para entrar al Google Drive):
Logeate a la cuenta a la que vayas a querer acceder en forma programática
Y cuando llegue la pregunta, incorporá como dependencia google/apiclient.
En nuestro caso, nos interesan las clases Google_Client (Necesaria para acceder a cualquier servicio de Google) y Google_Service_Gmail (Específica para el procesamiento de peticiones a Gmail).
La creación de la instancia de Google_Client requiere de la existencia de un set de credenciales válidas (que se obtienen simplemente siguiendo un enlace que generará la propia aplicación).
Una vez obtenido este objeto lo usaremos para crear nuestra instancia de la segunda clase:
$service = new Google_Service_Gmail($client);
Y a partir de ahí usaremos la propiedad users_messages para traer los correos.
Cómo procesar correos descargados de GMail usando PHP
Una vez obtenido cada uno de los correos necesitaremos procesarlos.
Lo que sabemos es que los correos vienen codificados como texto MIME, con lo cual, necesitaremos algo de ayuda para procesarlos sin volvernos locos… por ejemplo, la ayuda de PHPMimeMailParser.
Claro que no todo es tan fácil… en particular, los mails en Gmail vienen además codificados usando base64 (Pero no el base64 que viene con PHP… es un base64 codificado para URLs… Nada muy terrible, sólo hay que saberlo para actuar en consecuencia: se necesita cambiar los caracteres - y _ por + y /respectivamente. Más detalles en https://medium.com/@jrdnrc/decoding-gmail-messages-in-php-408194aeb767).
Una vez hecho esto podremos extraer alegremente las diferentes partes del correo, por ejemplo:
$from = $parser->getHeader('from');
Para conocer el remitente. O:
$subject = $parser->getHeader('Subject')
Para el asunto.
Resultado final
Ahora sí, veamos el ejemplo completo.
Como usé Composer para instalar las librerías, lo primero que voy a mostrarte es el archivo composer.json:
Si aún estás en dudas, no dudes más: usa la clase, te ahorrará una gran cantidad de dolores de cabeza.
Con lo que la llamada se vería algo así como:
<?php
$url = 'https://secure.softwarekey.com/solo/webservices/XmlCustomerService.asmx?WSDL';
$client = new SoapClient($url);
$xmlr = new SimpleXMLElement("<CustomerSearch></CustomerSearch>");
$xmlr->addChild('AuthorID', 1);
$xmlr->addChild('UserID', 'mchojrin');
$xmlr->addChild('UserPassword', '1234');
$xmlr->addChild('Email', 'mauro.chojrin@leewayweb.com');
$params = new stdClass();
$params->xml = $xmlr->asXML(); // OJO: La propiedad xml es particular de este WebService, debes reemplazarla por el nombre del parámetro que espera recibir el servicio al que buscas conectarte
$result = $client->CustomerSearchS($params);
print_r($result);
echo PHP_EOL;
Si ejecutas este código te encontrarás con algo como:
Lo que seguramente te interese es lo que está dentro de la clave any, con lo cual, para obtenerlo podrías usar echo $result->CustomerSearchSResult->any; en lugar de print_r($result);. Aunque probablemente lo que quieras no sea mostrar el resultado explícitamente, si no procesarlo de alguna manera… ¿qué mejor que recurrir nuevamente a SimpleXMLElement?
Ya en el artículo sobre cliente REST di una pequeña definición de un WebService, por si no lo leíste te lo cuento:
Un WebService es una pequeña aplicación web diseñada para interactuar con otras aplicaciones (en lugar de hacerlo con personas).
Las dos aplicaciones que se comunican toman el rol de:
Servidor: quien expone el servicio
Cliente: quien lo consume
¿Qué es REST?
REST es un protocolo de intercambio de información basado en HTTP.
¿Cómo se implementa en PHP?
Los servicios web basados en REST suelen ser mucho más fáciles de crear (y consumir) que los basados en SOAP.
De hecho, cualquier aplicación PHP que hayas hecho podría ser un WebService REST! (Bueno… tal vez no uno muy útil, pero eso es otro tema :)).
Te muestro un ejemplo super simple:
<?php
echo json_encode( [ 'Hola' ] );
No está mal, ¿cierto?
En este caso lo que vemos es una aplicación que, al ser invocada usando curl http://localhost:8080/rest_server.php(Asumiendo que está montada sobre el servidor local) nos dará esta salida:
["Hola"]
El cliente que haya realizado dicha invocación deberá saber qué tipo de contenido le estamos enviando (¡y actuar en consecuencia!).
Podés probarlo iniciando el servidor de esta forma:
php -S localhost:8080 &
(El & para que el proceso se ejecute en background y puedas seguir).
En caso de que se hubiese producido un error (Por ejemplo, que el recurso buscado no se encontrara disponible), deberíamos usar la función http_response_code para enviar un aviso al cliente:
http_response_code( 404 );
Por último, una buena práctica es también hacer explícito el tipo de contenido que vamos a enviar al cliente:
header('Content-type: application/json');
De esta forma el cliente tiene algo más de información y puede tomar mejores decisiones.
En definitiva, si podés elegir, te recomiendo usar siempre servicios web basados en REST. Otro consejo es que tengas a mano los códigos de error HTTP (No es necesario saberlos todos de memoria, pero ayuda :)).
¿Qué ejemplos se te ocurren ahora que sabés armar servicios REST?
Ya en el artículo sobre cliente SOAP di una pequeña definición de un WebService (Una más exhaustiva está en el curso de WebServices con PHP), por si no lo leíste te lo cuento:
Un WebService es una pequeña aplicación web diseñada para interactuar con otras aplicaciones (en lugar de hacerlo con personas).
Las dos aplicaciones que se comunican toman el rol de:
Servidor: quien expone el servicio
Cliente: quien lo consume
¿Qué es SOAP?
SOAP es un protocolo de intercambio de información basado en XML.
¿Cómo se implementa en PHP?
Ahora que estamos claros con las definiciones veamos un ejemplo:
server.php:
<?php
class MiClase
{
public function saludar()
{
return 'Hola ' . func_get_args()[0] . PHP_EOL;
}
}
try {
$server = new SoapServer(
null,
[
'uri'=> 'http://localhost:8080/soap_server.php',
]
);
$server->setClass('MiClase');
$server->handle();
} catch (SOAPFault $f) {
print $f->faultstring;
}
Para que todo esto tenga sentido, primero necesitamos tener un webserver levantado en localhost:8080. Para hacerlo simple, usemos el servidor incorporado al intérprete de PHP:
php -S localhost:8080 &
Y entonces, al ejecutar php soap_client.php veremos:
Hola mundo!
Si en lugar de publicar este script (server.php) en nuestro localhost lo subiéramos a un servidor accesible públicamente, cualquier aplicación conectarse a este servicio e invocar nuestro método saludar.
Puedes utilizar un archivo WSDL para darle más robustez al servicio (y hacerlo descubrible también), pero por el momento tienes todo lo necesario para permitir a otras aplicaciones interactuar con la tuya a través de un WebService SOAP.
En el desarrollo de aplicaciones para empresas es bastante común tener que trabajar con Excel (Ya sea importando planillas a bases de datos o bien lo inverso).
En general, la libería PHPSpreadSheet funciona muy bien para estos casos (Algo más de información aquí), sin embargo, el tratamiento de las fechas no es tan sencillo como esperamos.
Cómo Excel maneja las fechas
El problema radica en que el valor almacenado en la celda no es en realidad una fecha… si no la cantidad de días transcurridos desde el primero de Enero de 1900 (Si tenés curiosidad abrí el Excel y probá la fórmula «=DATEVALUE(‘1900-01-01’)»).
Esto provoca que, al hacer algo como:
$value = $worksheet->getCell('A1')->getValue();
Obtengamos un número entero (Generalmente grande) en lugar de una fecha
Cómo leer datos de tipo fecha con PhpSpreadsheet
Para resolver este pequeño inconveniente PhpSpreadsheet dispone de un método especial:
Este método tomará un objeto DateTime de php y lo convertirá a su correspondiente valor numérico para ser guardado sin problemas en una planilla de cálculo Excel.
Cuidado: que lo guarde bien no quiere decir que se vea bien al abrir el Excel.
Si lo dejas así verás algo como:
Que si bien técnicamente es correcto, muy probablemente no sea lo que esperarías.
No te preocupes, la solución es bien simple. Todo lo que tienes que hacer es establecer un formato para la celda que contiene la fecha.