PHP Simple HTML DOM Parser

Para abrir el mes de junio seguimos el hilo en nuestros artículos que buscan difundir el conocimiento libre del Patrimonio Tecnológico de la Humanidad. En anterior oportunidad estudiamos PHP curl, una herramienta basada en cURL y por ende en libcurl (¡ea, esto último no lo mencionamos allá!). Como en ese caso obtuvimos un método para descargar páginas web enteras, incluso si hay que pasarle datos con el método POST y/o hay que introducir usuario y contraseña. Esta entrada busca extraer, analizar e incluso modificar dichos datos ¡vente con nosotros!

PHP Simple HTML DOM Parser.

Presentación.

Buscábamos una “parser” o analizador código HTML y aunque PHP tiene su modelo establecido nos decantamos por algo más fácil pero con el inconveniente que debemos apoyar la licencia de uso del Instituto Tecnológico de Massachusetts (“MIT license”) y que NO viene integrado al lenguaje PHP. Lo bueno es que apenas son 65037 bytes en un archivo con un guion escrito en lenguaje PHP para que funcione (la licencia nos obliga a distribuir completo los archivos de ejemplos y manuales, todos escritos en idioma inglés).

El analizador de HTML (y XML) que veremos y aprenderemos a usar tiene el pomposo nombre de “PHP Simple HTML DOM Parsercon todas las implicaciones que derivan de llamar “simple” al D.O.M. (“Document Object Model”).

Document Object Model.

El Modelo de Objeto Documento es una norma propuesta por el Consorcio World Wide Web o W3C que son quienes hacen las recomendaciones y sientan las bases, de facto, del Web creado por el Doctor Tim Berners-Lee. En dicho papel de trabajo, a nuestro modo de ver las cosas, se propone que una página web es un documento y todo lo que está contenido en ella está representada por nodos. Si nos ponemos a ver tiene sentido porque la norma HTML5 establece unas sub divisiones que a su vez contienen títulos, sub títulos, párrafos, listas, etc.

Este concepto es mucho más amplio porque contempla, aparte de propiedades, también métodos que por definición HTML5 no contempla y para respetar la neutralidad hacen de la vista gorda del JavaScript (¿definirán algún día al CSS para suplantarlo? Sí, los sabemos, nuestro pensamiento siempre es profano).

El DOM está estructurado sobre tres pilares básicos:

  • “Core DOM”: modelo normativo para todos los tipos de documentos.
  • “XML DOM”: para documentos XML (febrero, 1998).
  • “HTML DOM”: no menos importante, aunque aparezca en tercer lugar, para documentos HTML.

Autores.

La idea original proviene de José Solorzano quien comenzó un proyecto bautizado como “HTML Parser for PHP 4” (dale con esto de los nombres largos ¿será que estoy leyendo muchos artículos en inglés y me estoy contagiando? Porque el idioma castellano es bastante prolijo…) que como podemos ver funciona con PHP 4 y pues va a ser que ya está descontinuado (incluso tienen un avisito recomendando el proyecto que vemos hoy acá).

Basado en ello S.C. Chen (me578022@gmail.com) programó una versión actualizada y basada en DOM con ayuda de Yousuke Kumakura y publicada en SourceForge, un alojador de contenido de software de código abierto que alberga más de 430.000 proyectos (3,7 millones de usuarios y 42 millones de clientes). Solo sabemos que S.C Chen es famoso a nivel mundial por su ópera prima que trajimos en este momento a colación.

Core DOM.

(pronto desarrollaremos este tema).

XML DOM.

Para poder entender el HTML DOM debemos primero estudiar y aprender el XML DOM y a continuación haremos nuestro racionamiento y sustento.

Concepto de XML.

“eXtensible Markup Language” o Lenguaje de Marcado Extensible (lo siento por el castellano pero lo seguiremos identificando como XML dado su impacto de facto en el mundo entero) es un lenguaje que “describe” como hacer las cosas y que además se puede ampliar en cualquier momento conservando una retrocompatibilidad. Fue diseñado para almacenar y transportar datos y que, además, pudiera ser leído por ordenadores y humanos por igual.

Si queréis leer sobre nuestra disertación acerca de lo que es un lenguaje de marcado, os invitamos a leer nuestro tutorial en línea sobre HTML. XML es guardado en ficheros codificados, generalmente, en UTF-8 para lograr compatibilidad con los diversos idiomas del mundo, nuestro tutorial sobre HTML5 también habla  y describe al respecto en forma detallada.

Normas XML.

Hay unas normas destacadas que mencionaremos y que a futuro, en la medida que tengamos tiempo para ello, le publicaremos sus diferentes entradas en nuestro humilde blog.

  • XML AJAX
  • XML DOM
  • XML XPath
  • XML XSLT
  • XML XQuery
  • XML DTD
  • XML Schema
  • XML Services

¿Qué es y qué NO es XML?

Aparte de lo que ya definimos, hay otros detalles que decir acerca del XML: fue diseñado para ser autodescriptivo, ya que a diferencia del HTML tiene muy pocos “comandos” o etiquetas y el XML como tal no hace absolutamente nada por sí solo. Esa es entonces la principal diferencia con HTML, que se enfoca en presentar los datos mientras que XML se enfoca en transportar datos.

Decimos que se puede extender ya que nosotros mismo “inventamos” nuestros propias etiquetas, así que no tenemos limitación pero una vez hallamos agregado más etiquetas a una información preexistente ésta seguirá siendo completamente leída y manipulada por distintas aplicaciones sin ningún tipo de problema.

Por supuesto, hacemos la advertencia que hablamos de extender, no de contraer: al eliminar algún dato por supuesto que las aplicaciones ya no podrán realizar su trabajo completo, y en muchos casos se negarán a realizarlo de plano.

Elementos de un documento XML.

Un documento XML está constituido por un elemento raíz el cual tiene elemento ramas o nodos que a su vez pueden contener subelementos y así sucesivamente. Cada elemento a su vez puede contener atributos.

De manera obligatoria un documento XML debe tener un elemento raíz, y aunque un prólogo no es obligatorio ni forma parte del documento es muy recomendable agregarlo; siempre va al principio del documento.

La función del prólogo es denotar la versión XML del documento y, de manera adicional, la codificación de caracteres el cual ya dijimos UTF-8 es lo más recomendable. La sintaxis del prólogo comienza con un símbolo “menor que” o mejor dicho, un corchete angular de apertura, ya que no estamos hablando de matemáticas en este caso (pero ayuda muchísimo a entender el concepto de aperura y cierre). Debe cerrar con un símbolo “mayor que” o corchete angular de cierre. Además de los dos símbolos anteriores se necesitan dos signos de interrogación de cierre y los contenidos deben estar entrecomillados para lo cual recomendamos las comillas simples, esto nos ahorra problemas para cuando vayamos a programar. Veamos un ejemplo de prólogo:

<?xml version='1.0' encoding='UTF-8'?>

El prólogo es un caso especial, y ya dijimos que no forma parte del documento, por lo tanto tiene distintas reglas que las aplicadas a los elementos, los cuales pasamos a describir en la siguiente sección.

Sintaxis de los elementos XML.

  • Cada uno de los elementos debe tener su correspondiente cierre pero con una barra invertida, por ejemplo:
    • <elemento> texto </elemento>
  • Los nombres de los elementos distinguen mayúsculas de minúsculas por lo tanto <elemento> no es igual a <Elemento>.
  • Los nombres de los elementos deben comenzar con una letra o en su defecto con un guion bajo ” _ “.
  • Los nombres de los elementos no pueden comenzar con “XML” o sus combinaciones de mayúsculas y/o minúsculas.
  • Los nombres de los elementos no puden contener espacios (de hecho un espacio denota que comienza un atributo, por eso no pueden contener espacios).
  • Los subelementos deben estar correctamente anidados dentro de los elementos, ejemplo:
    • <elemento><subelemento>valor</subelemento><elemento>
  • Los atributos de los elementos deben estar entrecomillados (lo mismo que dijimo para el prólogo se aplica en este caso), ejemplo:
    • <elemento principal=’si’> dato </elemento>
  • Como tal vez hayan captado, hay unos caracteres especiales en los documentos XML que no podremos usar directamente porque se presta a confusión para los ordenadores a la hora de leer el documento XML por lo tanto debemos sustituirlos por otros caracteres si queremos usarlos como datos:
    • Corchete angular de apertura “<” lo sustituimos por “&lt;” (recordad “lt”= “less than”, menos que).
    • Corchete angular de apertura “>” lo sustituimos por “&gt;“(recordad “gt”= “greater than”, mayor que).
    • Ampersand o “et” (proviene del latín, como en “etcétera”): “&” lo sustituimos por “&amp;“.
    • Apostrofo ” ” lo susituimos por “&apos;“.
    • Comillas dobles ” ” las sustituimos por “&quot;” (“quotation mark”).
  • Si queremos o necesitamos dejar un comentario para nosotros los seres humanos y que sea ignorado por el ordenador hacemos lo mismo que en el lenguaje HTML, lo encerramos entre los siguientes signos:
    • <!– comentario –>
    • Nota: dentro de un comentario no podemos escribir dos guiones juntos ya que dos guiones juntos indican inicio y fin de comentario, así que se prestaría a confusión para los ordenadores.
  • En los documentos XML debemos tener en cuenta que todo espacio es considerado como tal, es decir, los espacios se conservan y son un dato en sí mismo y así será leído ya quedará de parte de la aplicación el cortar o comprimir espacios “innecesarios”. Este comentario lo hacemos porque los documentos XML pueden ser leídos -y editados- por nosotros los humanos y un espacio en blanco mal colocado de nuestra parte hará que la aplicación que va  a leer los datos le suceda una excepción pero nosotros no veamos el error al abrir el archivo nosotros mismos.
  • Parecerá una tontería pero el caracter que indica el final de una línea es el caracter LF o “Line Feed” OSEA al caracter ASCII 10 y debemos tener siempre presente que en el sistema operativo Windows se utiliza LF más CR (ASCII 10 y 13 respectivamente) y en los viejos sistemas operativos Mac solo CR.

Características de los elementos de un documento XML.

  • Un elemento comienza desde el primer corchete angular de apertura hasta el último corchete angualr de cierre.
  • Un elemento puede contener:
    • Nada (pues eso, vacío, ni siquiera un espacio -si tuviera un espacio no sería vacio-).
    • Ya que introducimos el concepto de elemento vacio -y esto debería estar en la sintaxis- un elemento tácitamente vacio lo podremos denotar de la siguiente manera y ambos son iguales y válidos:
      • <elemento />
      • <elemento></elemento>
    • Texto -datos, para los ordenadores-.
    • Otros elementos -recordad anidar correctamente-.
    • Cualquier combinación de los tres anteriores, las combinaciones son infinitas y por ende decimos que son extensibles -y retrocompatibles-.

Una breve aclaratoria sobre los atributos.

Hay que tener especial cuidado con los atributos que le coloquemos a los elementos. Para no caer en abstracciones les haremos un caso práctico: consideremos un elemento llamado “persona” y lo que eso significa para nosotros los castellanohablantes.

<?xml version='1.0' encoding='UTF-8'?>
<persona></persona>

Ahora consideremos colocarle un atributo de género femenino o masculino:

<?xml version='1.0' encoding='UTF-8'?>
<persona sexo='masculino'>
    <nombre>Pedro</nombre>
</persona>
<persona sexo='femenino'>
   <nombre>María</nombre>
</persona>

Como vemos podemos “cargar” en memoria de ordenador solamente los hombres -o las mujeres- pero la aplicación que lee dichoa rchivo debe estar programada, de manera previa, a buscar esos dos géneros solamente ¿Qué sucedería si surgiera un tercer tipo de sexo, como por ejemplo hermafrodita?

<?xml version='1.0' encoding='UTF-8'?>
<persona sexo='masculino'>
    <nombre>Pedro</nombre>
</persona>
<persona sexo='hermafrodita'>
 <nombre>Michelle</nombre>
</persona>
<persona sexo='femenino'>
   <nombre>María</nombre>
</persona>

No habría forma ni manera que la aplicación pudiera “ampliarse” para que leyera datos adicionales ampliados, así que es una mejor idea plantearlo de la siguiente manera:

<?xml version='1.0' encoding='UTF-8'?>
<persona>
    <sexo>masculino</sexo>
    <nombre>Pedro</nombre>
</persona>
<persona>
    <sexo>hermafrodita</sexo>
    <nombre>Michelle</nombre>
</persona>
<persona>
    <sexo>femenino</sexo>
    <nombre>María</nombre>
</persona>

Como veís, colocado de esta manera la aplicación puede ir generando una matriz con los distintos nuevos tipos de valores tal como lo hace con los valores en sí mismos. Aparte de esta ventaja debemos recordar también que los atributos NO pueden contener múltiples valores y tampoco pueden tener estructuras tipo árbol, entonces ¿para que diantres sirven los atributos? Antes de responder esta pregunta queremos dejar en claro algo más sobre los atributos: los atributos son elemento fuertemente tipados -como decimos en los lenguajes de programación-, son estructura rígidas que son difíciles de cambiar a futuro pero que debido a esto podremos tomar ventaja; veamos la siguiente sección.

Eliminando ambigüedades en el nombrado de los elementos.

Como ya bien sabemos los documentos XML nos sirven para almacenar datos y debido a que nosotros los seres humanos asignamos un nombre para todo en nuestro universo -y a medida que lo seguimos descubriendo- no siempre escogemos nombres únicos. Tomemos por caso la palabra “banco”: tal vez primero pensemos en una institución financiera pero es que también un “banco” es un objeto sobre el cual podemos sentarnos.

Si por alguna idea peregrina nos damos a la tarea de guardar datos de ambos conceptos en un mismo documento XML debemos hallar una manera de diferenciarlos para que no haya lugar a dudas. Para ello podemos -y debemos- asignar un prefijo para eliminar cualquier duda, pero ¿qué prefijo usaremos? Pongamos por caso que los bancos -instituciones financieras- le colocamos el prefijo “i:” y los bancos -objetos- los prefijamos con “o:” (notemos que no violan la sintaxis de nombres de elementos ya que comienzan con una letra, no comienzan con “xml” ni contiene espacios):

<?xml version='1.0' encoding='UTF-8'?>
<raíz>
    <i:bancos>
        <i:nombre>Banco de Venezuela</i:nombre>
        <i:nombre>Banco Mercantil</i:nombre>
        <i:nombre>Banco Bicentenario</i:nombre>
    </i:bancos>
    <o:bancos>
        <o:nombre>banco de madera</o:nombre>
        <o:nombre>banco de metal</o:nombre>
        <o:nombre>taburete</o:nombre>
    </o:bancos>
</raíz>

Rapidamente notamos, si fueramos un lector ajeno que recibiera este documento XML, ¿qué diablos significa ambos prefijos, aparte de separarlos sintacticamente? Bien podríamos colocar un comentario para hacer alusión a qué nos referimos (“i:”institución financiera, “o:”obejto), pero eso funciona solo para nosotros los humanos, los ordenadores obvian estas observaciones ¿qué otra solución podemos echar mano?

Espacios de nombres en XML: XMLNS.

Los espacios de nombres (“Name Spaces”) en XML (juntos serían XMLNS) es el concepto para definir los prefijos que querramos usar en cualquier documento XML. Este “Name Spaces” son unos atributos y como tales debemos colocarlos en el elemento “padre” con un valor bajo la forma de URI (Uniform Resource Identifier) que bien puede ser a su vez un URL (Uniform Resource Locator) o un URN (Uniform Resource Name).

Para este nuestro caso vamos a usar el modelo de comentario y el modelo de URL todo esto apuntando siempre a que sea un ser humano el que va a leer la información (y luego veremos el caso de si es un ordenador el que “procesará” la información):

<?xml version='1.0' encoding='UTF-8'?>
<!--
  prefijo 'i:' se refiere a instituciones financieras.
  prefijo 'o:' se refiere a muebles, objetos.
-->
<raíz>
    <i:bancos xmlns:i='https://es.wikipedia.org/wiki/Banco'>
        <i:nombre>Banco de Venezuela</i:nombre>
        <i:nombre>Banco Mercantil</i:nombre>
        <i:nombre>Banco Bicentenario</i:nombre>
    </i:bancos>
    <o:bancos xmlns='https://es.wikipedia.org/wiki/Banco_(mueble)'>
        <o:nombre>banco de madera</o:nombre>
        <o:nombre>banco de metal</o:nombre>
        <o:nombre>taburete</o:nombre>
    </o:bancos>
</raíz>

También podemos declarar dichos atributos en el elemento raíz para darle mayor claridad a nuestro código y es igualmente válido:

<raíz
    xmlns:i='https://es.wikipedia.org/wiki/Banco'
    xmlns='https://es.wikipedia.org/wiki/Banco_(mueble)'
>

Recordemos que los documentos XML permiten los espacios en blanco para precisamente indentar para clarificar, por ello este formato de elemento raíz.

HTML DOM

Fuentes consultadas.

En idioma castellano.

En idioma inglés.

PHP y cURL

Suena incoherente: usamos curl en nuestra línea de comandos frecuentemente para diversidad de tareas pero no sabíamos que el lenguaje PHP tiene su propia versión llamada, cómo no, curl. El asunto es cómo se implementa y qué podemos hacer, nosotros en la línea de comandos lo acompañamos en el trabajo con grep pero en PHP la cosa es un poco diferente, ¡vamos a averiguar cómo!

Introducción.

Exactamente cómo implementaron cURL en PHP no lo sabemos, tal vez PHP haga llamado directo al curl y recoja sus resultados, sirviendo entonces como intermediario solamente… No tiene sentido pero si analizamos que muchos sitios web compartidos con otros dominios tienen PHP pero a uno no lo dejan ingresar por la línea de comandos entonces ¡vaya que si vale la pena el trabajo intermedio!

Por otra parte puede ser que especulamos demasiado y los buenos desarrolladores de PHP tienen su propia versión… ¡pero recordad que trabajamos con Software Libre que permite la modificación y reutilización del código! Pero echemos una leve ojeada al comando curl.

Comando curl.

Ya sabemos como va es esto del software libre: hay más de 1400 colaboradores en curl y el proyecto está alojado en GitHub desde el año 2010; aceptan contribuciones bajo la forma de “pull requests”: en resumen uno copia y trabaja con esa copia privada modificandola -y mejorandola, por supuesto- y devolviendo el trabajo al original para su aprobación. No se garantiza que se acepte toda la propuesta, e incluso ellos podrán tomar ciertas cosas propuestas y otras no, o en el mejor de los casos todo vaya bien y nos convirtamos en el colaborador 1401… y así haremos nuestra contribución al Patrimonio Tecnológico de la Humanidad.

Pero ¿quiénes desarrollan curl? Daniel Stenberg -sueco, habitante de Estocolmo- es el principal desarrollador y comenzó el proyecto por allá en 1996 basado en el trabajo de un programador brasileño llamado Rafael Sagula. Esta sencilla herramienta herramienta carioca fue bautizada como httpget con unas cuantas centenares de líneas y Stenberg las amplió para liberarla en 1997 con el nuevo nombre de “HTTP proxy support”. Pero con el advenimiento de “nuevos” protocolos al proyecto tales como GOPHER y FTP pronto dejó de tomar sentido llamarlo solamente “HTTP”… así que ese mismo año vio la luz la versión número dos.

Para 1998 más protocolos y capacidades fueron agregados así que volvió a cambiar de nombre para la versión 4.0 -manteniendo la numeración de versiones- así que el 20 de marzo de ese año marca el nacimiento formal de curl. El nuevo nombre hace alusión al programa del lado del cliente, de allí la letra “c” inicial. Las otras tres letras os lo podéis imaginar ya: Uniform Resource Locator -URL- o Localizador de Recursos Normalizados.

El mismo Stenberg reconoce que hay muchos proyectos con el mismo nombre curl pero para la época que ellos lanzaron la versión cuatro no había (o conocían) de otros proyectos con el mismo nombre. Es por ello que nosotros al principio especulamos del origen de curl en el lenguaje PHP, pero es que ¡Incluso existe un curl desarrollado en software privativo! (no le haremos publicidad pues no nos pagan por ello, buscadlo vosotros mismos con DuckDuckGo). Tan famoso es que ya se considera como verbo en idioma inglés y muchos creen que es un protocolo pues lo tratan como tal pero ya sabemos que en realidad es una navaja suiza con muchísimas funciones.

curl para la línea de comandos.

En principio el comando curl fue desarrollado para scripts ya que sus entradas y salidas utilizan las ya famosas stdin y stdout. No ampliaremos más porque en este blog hallaréis información sobre su uso con la línea de comandos. El artículo que motivó la publicación de esta entrada está en el siguiente “tuit” y precisamente es para la línea de comandos -y pretendemos que corra en PHP curl-:

También hemos recopilado con el paso del tiempo otros usos notables del curl en la línea de comandos:

 

curl para su uso como librería.

A partir del año 2000 fueron desarrolladas, o mejor dicho, el código existente fue migrado completamente como librerías y a partir de allí se le hizo una interfaz de usuario para la línea de comandos. Está escrito en lenguaje C y aún hoy en día dichas bases siguen sustentando el proyecto con la ventaja que se puede reutilizar en otros proyectos y compilarlos todos juntos. Seguimos sospechando que precisamente eso hicieron en PHP pero esa es nuestra humilde opinión que como reafirmamos: en software libre este comportamiento es un honor a su filosofía de desarrollo: ejecutar, estudiar, distribuir, mejorar y redistribuir.

Versiones actuales y desarrolladores estrellas.

Nosotros en nuestro GNU/Linux Ubuntu 16 tenemos instalada la versión 7.40.00 pero en realidad la última versión estable es la 7.54.0.

Si bien son muchísimos los colaboradores, es justo mencionar los que más han contribuido en los últimos años de una manera marcada y constante:

  • Daniel Stenberg.
  • Steve Holme.
  • Jay Satiro.
  • Dan Fandrich.
  • Marc Hörsken.
  • Kamil Dudka.
  • Alessandro Ghedini.
  • Yang Tse.
  • Günter Knauf.
  • Tatsuhiro Tsujikawa.
  • Patrick Monnerat.
  • Nick Zitzmann.

Disipación de toda duda.

En este punto de nuestra investigación nuestras sospechas se hacen realidad: uno de los primero lenguajes en adoptar la librería curl es precisamente PHP el cual motoriza un 25% de las páginas web a nivel mundial. El extracto , en inglés, reposa en el libro electrónico -liberado con licencia “Creative Commons”- “Everything about curl“:

The libcurl binding for PHP was one of, if not the, first bindings for libcurl to really catch on and get used widely. It quickly got adopted as a default way for PHP users to transfer data and as it has now been in that position for over a decade and PHP has turned out to be a fairly popular technology on the Internet (recent numbers indicated that something like a quarter of all sites on the Internet uses PHP).
A few really high-demand sites are using PHP and are using libcurl in the backend. Facebook and Yahoo are two such sites.

La traducción hecha por nosotros al idioma castellano:

El software de enlace (librerías) de curl para PHP fue uno, cuidado sino, el primero de los enlaces que realmente captura y lo usa ampliamente. Rápidamente fue adoptado como una manera por defecto para transferir datos y se ha mantenido en esa posición por más de una década mientras tque PHP se ha convertido en una tecnología bastante popular en internet (cifras recientes indican que algo asó como una cuarta parte de todos los sitios web en internet utilizan PHP).

Realmente unos pocos sitios web de alta demanda están usando PHP acompañado de las librerías de enlace de curl corriendo entre bamabalinas. Facebook y Yahoo son dos de tales sitios.

Protocolos soportados por curl.

HTTP, HTTPS, FTP, FTPS, GOPHER, TFTP, SCP, SFTP, SMB, TELNET, DICT, LDAP, LDAPS, FILE, IMAP, SMTP, POP3, RTSP y RTMP (esos son todos, por ahora -y vienen más, no se detiene el proyecto-).

PHP curl.

Ahora si que pasamos a hablar del software que nos ocupa en este vuestro sitio web de compartición del conocimiento. Como ya hemos hablado bastante de historia y teoría pasamos directamente a describir cómo trabajar con PHP curl:

  1. Inicializar curl con curl_init().
  2. Pasarle los parámetros – esencial es la URL – con curl_setopt().
  3. Retribuir y mostrar -o guardar- con curl_exec().
  4. Cerrar y liberar recursos con curl_close().

Nuestro primer ejemplo práctico.

Lo siguiente que haremos es abrir una ventana terminal, tomar nuestro editor de texto favorito y escribir el siguiente código para ser guardado en un archivo que llamaremos php_curl.php:

<?php
    $objCurl = curl_init();
    curl_setopt ($objCurl, CURLOPT_URL, "http://www.soporteweb.com");
    curl_exec ($objCurl);
    curl_close ($objCurl);
?>

Por supuesto, llamad vuestro archivo como queráis, guardadlo en vuestra carpeta donde ejecute vuestro servidor PHP y dadle los permisos de lectura y ejecución necesarios. En la primera línea inicializamos, en la segunda línea le pasamos la dirección web deseada -un dominio que devuelve nuestra dirección IP asignada por nuestro ISP-, en la tercera línea lo ejecutamos y en la cuarta línea cerramos y liberamos recursos. Una explicación más detallada a continuación.

Inicialización.

Debemos crear una instancia y guardarla para futuras referencias, este objeto basado en curl nosotros lo llamamos $objCurl y este nombre es el que debemos pasar a los otros comandos. El comando curl_init() solamente acepta un parámetro, la URL que es un dato imprescindible, tal vez debido a ser tan importante fue el único que establecieron en este comando. Aunque en el ejemplo no lo colocamos por razones didácticas, de ahora en adelante “para que no se nos olvide” lo estableceremos siempre en la primera línea del script o guion del programa.

Configuración.

Este es el comando más denso, notad que le damos el nombre de configuración porque su nombre así lo sugiere: curl_setopt(), osea “setopt” -> “set options”. Acepta tres parámetros, separados por comas:

  • Primero debemos indicarle el objeto que contiene la inicialización de curl, en nuestro caso la variable $objCurl.
  • Segundo le pasaremos el tipo de valor que le pasaremos en el tercer parámetro, en nuestro caso la URL y la constante que lo define -nombrada de manera nemotécnica- es CURLOPT_URL.
  • El tercer parámetro es el valor en sí mismo de lo que indicamos en el segundo parámetro.

Debemos indicaros que usamos una sola línea para configurar, pero pronto veremos que esta parte es la más abultada por la infinidad de datos y tipos de datos que podemos pasar. Es mejor que vayaís preparando para aprender que esta sección siempre será multilínea y que debemos indentarla y escribirla lo más explícito posible para corregir a futuro de forma fácil nuestro código.

Ejecución.

Simplemente le decimos a PHP que hemos terminado de establecer lo que queremos obtener –o lo que queremos enviar, ya veremos más adelante- y acepta un parámetro que sigue siendo el objeto que hemos creado y configurado. En este punto ya os recordaremos que estamos trabajando con funciones y este comando devuelve verdadero o falso para indicar si tuvo éxito (para comparar la respuesta usaremos siempre “==” para estar seguros del tipo de variable y el resultado obtenido). En el ejemplo no colocamos ninguna variable que reciba el resultado del que hablamos como función ¿dónde está lo que nos interesa? En realidad el valor numérico (verdadero o falso) es lo que podemos guardar en una variable, el resto del resultados se va al stdout y eso será lo que enviaremos al navegador web que solicitó nuestro primer guión PHP sobre curl. Debido a esto recibiremos el mismo código HTML de la página que estamos solicitando (pero con los enlaces web absolutos cambiados).

Si probáis con descargar diferentes páginas, unas funcionarán, otras no, todo depende de si la página es estática, dinámica, si tiene JavaScript, en fin, cantidad de cosas que pueden NO ser compatibles con PHP curl: por eso debemos aprender las muchísimas opciones de configuración que nos sean útil en cada trabajo que se nos presente para ganarnos así el pan nuestro de cada día como Dios manda.

Liberación de recursos.

Os podrá sonar que nos contradecimos con lo siguiente: la función curl_close() se encarga de cerrar, como su nombre indica pero no devuelve resultado acerca del trabajo que le mandamos a realizar. Consideramos que esto es un “bug” porque a la hora de detectar dónde fugan recursos en nuestro servidor deberemos recurrir a otras herramientas apartes de PHP, pudiendo ser evitado esto con una sencilla rutina de nuestra parte e indicar que está sucediendo y qué está mal. Ah, perdón, casi lo olvidamos: el parámetro único que acepta es el objeto que creamos -y queremos destruir- con curl_init().

Nuestro primer ejemplo práctico pero mejorado.

Un sencillísima rutina de control de errores nos puede ahorrar muchísimos dolores de cabeza.

Nota: hay mejores métodos para el manejo de excepciones, pero aprendamos poco a poco. Ah, y nosotros lo llamamos “control de errores” y eso tampoco es el nombre correcto (excepciones) pero así nos abstraemos.

Por ello reescribiremos nuestro ejemplo pero con condicionales if~else:

<?php
  $objCurl = curl_init("http://www.soporteweb.com/");
  if ($objCurl == true) {
     $resul = curl_exec ($objCurl);
     if ($resul == true){
       curl_close ($objCurl);
     } else {
       echo "No se pudo ejecutar PHP curl<br>";
     }
  } else {
    echo "No se pudo inicializar PHP curl.<br>";
  }
?>

Notad que pasamos de una buena vez el URL en en el inicio con curl_init(). Además guardamos el valor del resultado en una variable, evaluamos el valor que tiene y ejecutamos o mostramos un mensaje apropiado según la respuesta obtenida. Muchos programadores y programadoras piensan que hacer nuestro software de esta manera, aparte de ayudarnos a nosotros mismo, abre las puertas a los hackers cuando se presentan excepciones -o errores, como les llamamos- porque “develan mucho”. Ese razonamiento es válido en el software privativo pero en el software libre no tiene asidero alguno porque cualquier hacker tiene acceso al código fuente, así que hagamos la depuración fácil para nosotros mismos.

Guardando el resultado en una “variable aparte”.

Como explicamos PHP curl envía al stdout la respuesta y asu vez eso lo pasamos al navegador, ¿qué tal si analizamos primero lo que recibimos y luego lo reenviamos? Por ejemplo, podríamos “acomodar” los enlaces absolutos de las imágenes que contiene la página web que llamamos (URL). Para ello vamos a emplear culr_setopt() con CURLOPT_RETURNTRANSFER establecido en el valor 1, mirad:

<?php
    $objCurl = curl_init("http://www.soporteweb.com");
    if ($objCurl == true) {
        curl_setopt ($objCurl, CURLOPT_RETURNTRANSFER, 1);
        $resul = curl_exec ($objCurl);
        if ($resul == true){
            curl_close ($objCurl);
            print "Nuestra direccion IP es: ";
            print $resul;
        } else {
            echo "No se pudo ejecutar PHP curl<br>";
        }
    } else {
        echo "No se pudo inicializar PHP curl.<br>";
    }
?>

Y así “manipulamos” el resultado con el texto que coloreamos en verde; ahora vamos un paso más allá. 😎

Guardando el resultado en un archivo.

Aunque ya tengamos el resultado deseado en una “variable” supongamos que estamos creando un robot (o bot como los mientan ahora en este siglo) y queremos guardar el resultado en un archivo ¿qué tiene que ver PHP curl con esto si ya sabemos el lenguaje PHP y sabemos como hacerlo aparte?

Pues resulta que con CURLOPT_FILE en curl_setopt() permite guardarlo en un archivo, pero antes tenemos que abrir el archivo y pasarle la “referencia” a PHP curl:

<?php
  $objCurl = curl_init("http://www.soporteweb.com/");
  if ($objCurl == false) {
    print "No se pudo inicializar <b>PHP curl</b>.<br>";
  } else {
    $nom_arch = 'PHP_curl.html';
    $arch = fopen( $nom_arch , "w");
    if ($arch == false ) {
      print "No se pudo abrir el archivo '".$nom_arch."'<br>";
      curl_close ($objCurl);
    } else {
      $resp = curl_setopt ($objCurl, CURLOPT_RETURNTRANSFER, 1);
      if ( $resp == false ) {
        print "No se pudo establecer CURLOPT_RETURNTRANSFER.<br>";
        curl_close ($objCurl);
      } else {
        $resp = curl_setopt ($objCurl, CURLOPT_FILE, $arch);
        if ( $resp == false ) {
          print "No se pudo establecer CURLOPT_FILE.<br>";
          curl_close ($objCurl);
      } else {
          $resul = curl_exec ($objCurl);
          if ($resul == false) {
            echo "No se pudo ejecutar PHP curl.<br>";
          } else {
            print "El archivo fue guardado con el nombre 'PHP_curl.html'.<br>";
            // curl_close NO devuelve resultado:
            curl_close ($objCurl);
          }
        }
      }
    }
  }
?>

Si tenéis problemas para escribir el archivo en disco REVISAD vuestros derechos de escritura para PHP especialmente para el grupo “www-data”.

Así de sencillo, con las correspondientes rutinas de depuración para nosotros. Como tal vez se vea un poco largo hemos coloreado las partes importantes para que sigáis el hilo. ¿Que por qué nos hemos ido por la negación siempre de primero? Pues revisando la función fopen() verificamos que si es exitosa devuelve una referencia al archivo más no un valor de tipo booleno -verdadero-. En cambio si falla la función siempre devuelve un valor boleano falso. Lo de seguir usando la negación por defecto es para llevar el mismo estilo parejo al resto del código.

Mejorando la claridad de la sintaxis con ayuda de exit().

Una función “vieja” en PHP es la función die() que es totalmente equivalente a exit() que es más “elegante”. El chiste del asunto es emplear la conjunción or (operador lógico “o”) para unir la acción que queremos realizar con la función exit(). Dicha función permite como parámetro una cadena de texto o un entero entre cero y 254 (el 255 está reservado para uso exclusivo de PHP). Nos gusta mejor la opción de pasarle una cadena de texto con el mensaje que queremos presentar al usuario ¡o a nosotros mismos!

Otro punto importante es que la función exit() aparte de mostrar mensaje y cerrar adecuadamente los objetos en memoria es que sale inmediatamente y no ejecuta el resto de las líneas que quedan a partir de su llamada.

Veamos entonces cómo emplearlo en nuestra labor que hoy nos ocupa (para no alargar tanto cada línea fijaos el uso del punto y coma para separar bloques de código multilíneas):

<?php
  $objCurl = curl_init("http://www.soporteweb.com/")
    or exit("No se pudo inicializar <b>PHP curl</b>.<br>");

  $nom_arch = 'PHP_curl.html';
  $arch = fopen( $nom_arch , "w")
    or exit("No se pudo abrir el archivo '".$nom_arch."'<br>");

  curl_setopt ($objCurl, CURLOPT_RETURNTRANSFER, 1)
    or exit("No se pudo establecer CURLOPT_RETURNTRANSFER.<br>");

  curl_setopt ($objCurl, CURLOPT_FILE, $arch)
    or exit("No se pudo establecer CURLOPT_FILE.<br>");

  curl_exec ($objCurl)
    or exit("No se pudo ejecutar PHP curl.<br>");

  print "El archivo fue guardado con el nombre 'PHP_curl.html'.<br>";

  // curl_close NO devuelve resultado:
  curl_close ($objCurl);
?>

¡Qué gran mejora en la legibilidad! Debemos aclarar que exit() de manera implícita llama a los procedimientos de cierre y liberación de recursos de memoria (destructor lo nombran en lenguaje C++) así que como ven es una vía muy pragmática para nuestros propósitos.

Si queréis probar como funciona la rutina con control de excepciones, colocad una dirección web que no exista y mirad que sucede, ¡experimentad! También os recomendamos probar otros protocolos, por ejemplo FTP en un servidor público como por ejemplo “ftp://ftp.cesca.es/” donde veremos un listado de archivos disponibles para ser descargados.

Probando otros protocolos: FTP.

En el párrafo anterior os sugerimos probar el protocolo FTP y no hubo nada que cambiar en el código que teníamos -aparte de la URL, por supuesto-. Pero PHP curl tiene sus opciones y a continuación pasamos a revisar alguna de ellas. La primera que revisaremos limita el listado obtenido de archivos y directorios, osea, no muestra los permisos de cada uno de ellos. La sintaxis en el comando curl_setopt() -y todas las opciones utilizan esta función- es la siguiente:


1
2
curl_setopt($objCurl, <span style="color: #008000;">CURLOPT_FTPLISTONLY</span>, 1)
  or exit("No se pudo establecer CURLOPT_FTPLISTONLY");

Si el servidor FTP no fuera público o exigiera una conexión tipo anónimo usaremos esto:

 curl_setopt($objCurl, CURLOPT_USERPWD, "anonimo:su@correo-e.com")
    or exit("No se pudo establecer CURLOPT_USERPWD");

Con esta novedad de introducir credenciales de conexión nos encontramos con otro tipo de manejo de excepciones: dado el caso las credenciales no funcionen (mala escritura, contraseña vencida, usuario inexistente, etc) dentro del obejto PHP curl se guardará en una instancia aparte el resultado del intento de conexión, para ellos contamos con:

 echo curl_error($objCurl);

Pasando valores por medio del método POST.

Finalmente llegamos al meollo del asunto: poder pasar a un servidor web una o más variables por medio del método POST a un servidor web. Para propósitos de aprendizaje diremos que es uno de los métodos más populares debido a su cierta privacidad ya que el usuario no puede ver el enlace como en el método GET y tampoco es “cacheable” por los navegadores web.

Primero haremos una rchivo que nos muestre todas las variables que reciba y las liste por pantalla, de manera increíble solo necesita una sola líneas de código (guardar con el nombre de “curl_post.php“):

<?php
  var_dump($_POST);
?>

Una vez tengamos este archivo ya sea en nuestro servidor local o en nuestro servidor remoto podremos comenzar a escribir el siguiente script:

<?php
 $objCurl = curl_init("http://localhost/curl_post.php")
 or exit("No se pudo inicializar PHP curl<br>");

 curl_setopt($objCurl, CURLOPT_POST, 1)
 or exit("No se pudo establecer CURLOPT_POST");

 //Pasamos las variables que nos interesan al servidor
 curl_setopt($objCurl, CURLOPT_POSTFIELDS, "var1=uno&var2=dos&var3=tres")
 or exit("No se pudieron establecer las variables en CURLOPT_POSTFIELDS");

 curl_exec ($objCurl)
 or exit("No se pudo ejecutar PHP curl.");

 curl_close ($curl);
?>

Debido a los mensaje que incluimos en cada exit() y con el coloreado del código no hay nada que explicar… excepto unas cuantas acotaciones:

  • Las variables que pasamos en el método POST deben estar por pares unidas con el ampersand y separadas por medio de un signo de igualdad.
  • Los espacios no se permiten, debemos pasar “%20” o mejor dicho debemos darle un formato especial antes de enviarlo. El comando que nos ayudará será urlencode() y para nosotros castellanohablantes es importante representar bien los acentos, etc.
 //Pasamos las variables que nos interesan al servidor
 $var_post = urlencode("var1=Venezuela&var2=América del Sur&var3=Güigüe");
 curl_setopt($objCurl, CURLOPT_POSTFIELDS, $var_post)
   or exit("No se pudieron establecer las variables en CURLOPT_POSTFIELDS");

Fuera de tema: “Tamper data for Mozilla Firefox”.

Adam Judson es un desarrollador que tiene -nos tiene- como usuarios a más de 80 mil personas en su trabajo de programación desde el año 2007. Él desarrolló “Tamper Data” para este popular navegador, una herramienta para, según sus propias palabras “pruebas de seguridad en aplicaciones web por medio de la modificación de los parámetros POST”.

Dicha herramienta la podemos instalar en nuestro navegador (requiere reinicio del navegador y no es compatible con “Google Web Accelerator”) y con ella podremos visitar cualquier sitio web que utilize envio de datos POST y al activarlo en una ventana aparte mostrará sin molestar ni influir en nada el tráfico saliente de nuestro ordenador por medio de nuestro navegador web Firefox. ¿Por qué anuncia pruebas de seguridad? Porque nosotros muy bien sabemos como programadores que el código fuente de nuestras aplicaciones web están a la vista de los usuarios ¿pero que tal lo que no se ve? Pues acá entra “Tamper Data”: por más que coloquemos validación HTML5 y JavaScript del lado del cliente, si no utilizamos HTTPS el tráfico entre el usuario y nuestro servidor puede ser modificado por terceros. Y aunque si usaramos HTTPS tampoco podemos confiarnos de nuestros usuarios, verbigracia la misma herramienta que estamos recomendando puede modificar los datos si en la venta que se nos abre hacemos click en “modificar”. Al activar este botón, cuando sale algo de nuestro ordenador nos pregunta si deseamos modificar algún dato, ¡y allí podremos echar por tierra toda la hermosa programación hecha en HTML5 y javaScript!

Este comentario es “fuera de tema”: para combatir esto último lo que debemos hacer es, en lenguaje PHP -o vuestro lenguaje utilizado- volver a validar los datos recibidos desde el usuario, ¡PERO HAY MÁS! Es obligatorio agregar “disparadores” y “restricciones” incrustados en nuestra base de datos (recomendamos PostgreSQL por su capacidad de incluso aceptar guiones en lenguaje Python) para volver a validar datos al agregar los datos a nuestro motor informático. Más detalles escapan de esta entrada, pero este resumen es el mejor que podemos expresar al respecto.

Ejecutando PHP curl en modo de depuración.

“Verbose” en idioma inglés describe un modo de hablar más de lo necesario. Pero en nuestro mundo del software libre nuestra búsqueda del conocimiento es insaciable, “solo sabemos que no sabemos nada” por ello le dedicamos esta sección empinada a ahondar en PHP curl. Aparte de aprender más y mantener nuestro cerebro haciendo ejercicios, debido a la gran cantidad de protocolos y opciones es sumamente fácil para nosotros el cometer errores solicitando opciones inexistentes o incompatibles entre ellas, si son varios los parámetros que les pasemos.

Para activar el modo de depuración -vamos que la traducción al castellano de “verboso” no cuela- en PHP curl echamos mano de la alternativa CURLOPT_VERBOSE en la ya consabida función curl_opt() ¡pero esperen -como dice el “infomercial” de T.V.- hay más! PHP curl, dijimos, lo tenemos claro, emplea stdin y stdout que son los más conocidos pero también utiliza stderr ¡para algo que no es error, sino DEPURACIÓN! Teoricamente no debería ser pero en la práctica, el mundo real esto es de facto que lo hacen -y haremos-: capturar el stderr para llevar los mensajes de depuración hacia un archivo de escritura y de adición. Este archivo si no está creado, lo hace, y lo abre para agregar dichos datos y si existe le adiciona al final, de tal manera que llevamos una especie de bitácora para nuestro estudio y control.

<?php
  $ObjCurl = curl_init()
    or exit("No se pudo inicializar PHP curl.<br>");

  curl_setopt ($ObjCurl, CURLOPT_URL, "http://www.soporteweb.com")
    or exit("No se pudo establecer la URL.<br>");

  curl_setopt($ObjCurl, CURLOPT_VERBOSE, 1)
     or exit("No se pudo establecer el modo de depuración.<br>");

  $verbose_arch = fopen('verbose.txt', 'a');
  curl_setopt($ObjCurl, CURLOPT_STDERR, $verbose_arch)
    or exit("No se pudo establecer CURLOPT_STDERR<br>.");

  curl_exec ($ObjCurl)
    or exit("No se pudo ejecutar PHP curl.<br>");

  curl_close ($ObjCurl);
?>

Y veremos algo parecido a esto:

CURLOPT_VERBOSE
CURLOPT_VERBOSE

Explicamos que:

  • Las líneas que comienza con un asterisco son mensajes informativos de PHP curl.
  • Las líneas que comienzan con “>” es información que se le envia al servidor web, ftp, etc.
  • Las líneas que comienzan con “<” es la información que recibimos de tal servidor.
  • Los servidores web basados en ASP.NET devolverán un error “HTTP/1.1 500 Internal Server Error” y no podremos hacer nada ya que no se rigen por las normas comunes basadas en RFC y recomendaciones de WWW.

Otros usos para PHP curl.

Por nuestros experimentos vimos que PHP curl permite grabar en archivos “flujos de datos” de una manera básica, sin estar amarrado a protocolos (si lo usamos con FTP veremos que nos hacen falta los comandos populares y que solo podremos usar si tenemos un cliente FTP como tal). Eso deja abierta la posiblidad a darle infinidad de usos sin siquiera saber -ni preocuparnos- “con quien estamos hablando”. Un ejemplo de ello es con el acceso a servidores con tecnología ASP.NET o la mismisa consulta de nuestra dirección IP que devuelve un resultado no normalizado ni con metadato alguno (solo nosotros, seres humanos, estamos con la plena certeza que es una dirección IPv4).

Por ejemplo, si en vez de usar soporteweb.com para retribuir nuestra dirección IPv4 usamos el servicio que presta icanhazip.com en el modo de depuración veremos algo como esto:

icanhazip.com Get the facts
icanhazip.com Get the facts

Y al seguir el enlace indicado abrimos el blog de Major Hayden (sí, ese es el nombre, no no es mayor de ningún ejército ni alcalde de ninguna ciudad) un administrador de sistemas GNU/Linux con 6 años de experiencia quien ha tenido excelentes maestros y está dispuesto siempre aprender… como nosotros mismos, quienes escribimos y quienes leemos este humilde sitio web.

Auguramos que curl y PH curl seguirá creciendo y mejorando para nuestro beneplácito y que nosotros, a la larga, algún día aportaremos nuestro granito de arena a la causa del software libre, de una u otra manera, de manera inexorable.

curl_setopt(): parámetros mas socorridos.

La lista es larga en verdad pero mencionaremos, al menos, los que avizoramos de alguna utilidad dada la experiencia que hemos logrado a lo largo de los años:

CURLOPT_USERAGENT

Con esta opción podremos indicarle al servidor con el cual le estamos requiriendo información acerca de nuestra identidad. Curiosamente PHP curl por defecto no envía indentificación alguna acerca del nombre del software pero es que también el servidor que atiende la consulta tampoco la requiere como obligatoria. En la práctica les mostramos (las direcciones IP las ocultamos -junto con otros detalles- de las visitas a esta vuestra página web):

PHP curl no se identifica -por defecto-
PHP curl no se identifica -por defecto-

Así que le aplicamos código y así quedaría la visita de nuestro bot en cierne:

Usando PHP curl CURLOPT_USERAGENT
Usando PHP curl CURLOPT_USERAGENT

CURLOPT_CONNECTTIMEOUT

A veces estimamos en cuánto tiempo debería responder determinado proceso y si no se realiza pues no está bien afinado nuestro servidor no sin antes descartar fallas de internet, etc. Para ello CURLOPT_CONNECTTIMEOUT es el idóneo para que nuestro script no se cuelgue indefinidamente, al cabo de cierto tiempo que le establezcamos y si no logra conectar con el servidor la tarea finalizará. El tercer parámetro que pasaríamos a continuación es el número de segundos, lo cual es una eternidad en tiempo de computadores, por ello también contamos con CURLOPT_CONNECTIONTIMEOUT_MS que viene expresado en milisegundos si estamos realmente apurados.

Es una excelente opción para desarrollar herramientas de diagnóstico a equipos remotos y podremos llevar un ergistro de eventos en nuestra base de datos (sí, que ya hay mucho software para eso pero también hay tareas tan pequeñas que no necesitamos “una mandarria sino un simple martillito”)

CURLOPT_COOKIE y CURLOPT_COOKIEFILE

Una “cookie” en inglés o galletita en castellano, es una pieza de información que plantan los servidores web en nuestros navegadores para identificarnos de manera absoluta. Al igual que el método POST debemos saber el nombre de la galletita y por ende su contenido, así que no la generamos nosotros -obvio- pero si debemos tener una para poder obtener respuesta de algunos servidores, quienes comparan si tienen la galletita en su lista de memoria (las galletitas tienen vencimiento o validez en el tiempo) y así podrán:

  • bien sea aceptar nuestra petición ó
  • incluso devolvernos unos valores personalizados.

De allí viene el gran temor a la violación de nuestra privacidad, una delgada línea demarca las buenas de las malas intenciones.

De esto último viene la necesidad de CURLOPT_COOKIEFILE, muchas veces son tantas las galletitas usadas por nosotros los programadores que mejor es enviar un archivo completo con ellas incluídas. Aquí debemos colocar el nombre del archivo en la opción anterior debemos enviar los valores en sí.

Otra manera de pasar una gran cantidad de galletitas es pasar el jarrón completo, un fichero, y para que no se nos olvide la sintaxis le pusieron el curioso nombre CURLOPT_COOKIEJAR. Un ejemplo sería:

curl_setopt($objCurl, CURLOPT_COOKIEJAR, 'jarron_de_galletitas.txt');

1
CURLOPT_SSL_VERIFYPEER

Con esta opción podemos deshabilitar que verifique certificados en una conexión https (o mejor dicho TLS). Esto normalmente está habilitado pero uno a veces necesita, por economía, un certificado autofirmado el cual no está validado ante ningún tercer ente. Con esta opción cambiada a false en el tercer parámetro evitaremos este problemita.

1
CURLOPT_FOLLOWLOCATION

Actualmente las redirecciones de página se ha vuelto el pan nuestro de cada día motivado a la popularidad de las páginas web. Una página de cualquier gobierno del mundo de muy seguro que tendrá miles y hasta millones de visitantes y como comprenderán un solo servidor web jamás ni nunca se daría abasto. Lo que se ha inventado es poner granjas de servidores en distintas partes del mundo con copias constantes de los dominios deseados y redireccionado el tráfico con ayuda de potentes DNS: todo esto se conoce como CDN o “Content Delivery Network”.

La idea del asunto es que cuando uno solicita una página web los DNS encargados redireccionan nuestra solicitud a la granja de servidores más cerca a nosotros de manera geográfica donde habrá una copia de la web que queremos. ¿A qué viene todo este cuento con PHP curl? Que es muy probable que la respuesta que obtengamos será una cabecera tipo 300 indicandonos que la direcciíon adecuadad para nosotros y entonces volvemos a hacer la petición -la misma petición- pero a otro servidor más cercano (o mas desocupado, o que tenga nuestra idioma, etc. -el que nos convenga-).

PHP curl ya está preparado en ese escenario y estableceremos CURLOPT_FOLLOWLOCATION a verdadero y por defecto PHP curl redireccionará hasta un máximo de 5 oportunidades nuestra solicitud (más adelante veremos otra función que nos dirá cuántas redirecciones siguió con la variable CURLINFO_REDIRECT_COUNT establecida).

Un problema se presenta al hacer una consulta POST: después del primer redireccionamiento PHP curl NO HACE una consulta POST sino una consulta GET (por razones históricas y de costumbres en los navegadores) y esto es un problema porque son métodos distintos y no obtendremos respuesta (aparte de que nos volveremos “locos” buscando dónde está el error). Para prevenir este inconveniente en vez de fijar CURLOPT_POST a “true” tomaremos otra opción, CURLOPT_CUSTOMREQUEST fijado a “POST”:

curl_setopt($ObjCurl, CURLOPT_CUSTOMREQUEST, "POST");

Otra advertencia más: cuando uno sube archivos por medio de CURLOPT_POSTFIELDS en la segunda redirección no será enviado dicho fichero. También para prevenir esto urge emplear CURLOPT_POSTREDIR fijado el valor a 3 para que maneje los redireccionamientos 301 y 302 reenviando el archivo en cada oportunidad (es más tráfico de datos pero ¿cómo hacemos con los CDN’s que llegaron para quedarse?).

Panorama general del resto de las opciones de curl_setopt()

Ya en este punto os habreis dado cuenta de la importancia de esta función, recordad que solo hemos visto apenas cuatro que van en este orden: curl_init(), curl_setopt(), curl_exec() y curl_close(); pues bien hay 23 funciones más de las cuales más adelante estudiaremos una de ellas (por ahora).

He aquí un listado clasificado por el tipo de contenido que hay que pasar en el tercer parámetro de curl_setopt(), de un vistazo (en marrón las que hemos visto y analizado):

Aquellas cuyo valor debe ser booleano (verdadero<>0 ó falso=0):
  • CURLOPT_AUTOREFERER
  • CURLOPT_BINARYTRANSFER
  • CURLOPT_COOKIESESSION
  • CURLOPT_CERTINFO
  • CURLOPT_CONNECT_ONLY
  • CURLOPT_CRLF
  • CURLOPT_DNS_USE_GLOBAL_CACHE
  • CURLOPT_FAILONERROR
  • CURLOPT_SSL_FALSESTART
  • CURLOPT_FILETIME
  • CURLOPT_FOLLOWLOCATION
  • CURLOPT_FORBID_REUSE
  • CURLOPT_FRESH_CONNECT
  • CURLOPT_FTP_USE_EPRT
  • CURLOPT_FTP_USE_EPSV
  • CURLOPT_FTP_CREATE_MISSING_DIRS
  • CURLOPT_FTPAPPEND
  • CURLOPT_TCP_NODELAY
  • CURLOPT_FTPASCII
  • CURLOPT_FTPLISTONLY
  • CURLOPT_HEADER
  • CURLINFO_HEADER_OUT
  • CURLOPT_HTTPGET
  • CURLOPT_HTTPPROXYTUNNEL
  • CURLOPT_MUTE
  • CURLOPT_NETRC
  • CURLOPT_NOBODY
  • CURLOPT_NOPROGRESS
  • CURLOPT_NOSIGNAL
  • CURLOPT_PATH_AS_IS
  • CURLOPT_PIPEWAIT
  • CURLOPT_POST
  • CURLOPT_PUT
  • CURLOPT_RETURNTRANSFER
  • CURLOPT_SAFE_UPLOAD
  • CURLOPT_SASL_IR
  • CURLOPT_SSL_ENABLE_ALPN
  • CURLOPT_SSL_ENABLE_NPN
  • CURLOPT_SSL_VERIFYPEER
  • CURLOPT_SSL_VERIFYSTATUS
  • CURLOPT_TCP_FASTOPEN
  • CURLOPT_TFTP_NO_OPTIONS
  • CURLOPT_TRANSFERTEXT
  • CURLOPT_UNRESTRICTED_AUTH
  • CURLOPT_UPLOAD
  • CURLOPT_VERBOSE
Aquellas cuyo valor debe ser numérico entero:
  • CURLOPT_BUFFERSIZE
  • CURLOPT_CLOSEPOLICY
  • CURLOPT_CONNECTTIMEOUT
  • CURLOPT_CONNECTTIMEOUT_MS
  • CURLOPT_DNS_CACHE_TIMEOUT
  • CURLOPT_EXPECT_100_TIMEOUT_MS
  • CURLOPT_FTPSSLAUTH
  • CURLOPT_HEADEROPT
  • CURLOPT_HTTP_VERSION
  • CURLOPT_HTTPAUTH
  • CURLOPT_INFILESIZE
  • CURLOPT_LOW_SPEED_LIMIT
  • CURLOPT_LOW_SPEED_TIME
  • CURLOPT_MAXCONNECTS
  • CURLOPT_MAXREDIRS
  • CURLOPT_PORT
  • CURLOPT_POSTREDIR
  • CURLOPT_PROTOCOLS
    • CURLPROTO_HTTP, CURLPROTO_HTTPS, CURLPROTO_FTP, CURLPROTO_FTPS, CURLPROTO_SCP, CURLPROTO_SFTP, CURLPROTO_TELNET, CURLPROTO_LDAP, CURLPROTO_LDAPS, CURLPROTO_DICT, CURLPROTO_FILE, CURLPROTO_TFTP, CURLPROTO_ALL
  • CURLOPT_PROXYAUTH
  • CURLOPT_PROXYPORT
  • CURLOPT_PROXYTYPE
  • CURLOPT_REDIR_PROTOCOLS
  • CURLOPT_RESUME_FROM
  • CURLOPT_SSL_OPTIONS
  • CURLOPT_SSL_VERIFYHOST
  • CURLOPT_SSLVERSION
  • CURLOPT_STREAM_WEIGHT
  • CURLOPT_TIMECONDITION
  • CURLOPT_TIMEOUT
  • CURLOPT_TIMEOUT_MS
  • CURLOPT_TIMEVALUE
  • CURLOPT_MAX_RECV_SPEED_LARGE
  • CURLOPT_MAX_SEND_SPEED_LARGE
  • CURLOPT_SSH_AUTH_TYPES
  • CURLOPT_IPRESOLVE
    • CURL_IPRESOLVE_WHATEVER (por defecto), CURL_IPRESOLVE_V4, CURL_IPRESOLVE_V6
  • CURLOPT_FTP_FILEMETHOD
    • CURLFTPMETHOD_MULTICWD, CURLFTPMETHOD_NOCWD, CURLFTPMETHOD_SINGLECWD.
Aquellas cuyo valor debe ser una cadena de texto:
  • CURLOPT_CAINFO
  • CURLOPT_CAPATH
  • CURLOPT_COOKIE
  • CURLOPT_COOKIEFILE
  • CURLOPT_COOKIEJAR
  • CURLOPT_CUSTOMREQUEST
  • CURLOPT_DEFAULT_PROTOCOL
  • CURLOPT_DNS_INTERFACE
  • CURLOPT_DNS_LOCAL_IP4
  • CURLOPT_DNS_LOCAL_IP6
  • CURLOPT_EGDSOCKET
  • CURLOPT_ENCODING
  • CURLOPT_FTPPORT
  • CURLOPT_INTERFACE
  • CURLOPT_KEYPASSWD
  • CURLOPT_KRB4LEVEL
  • CURLOPT_LOGIN_OPTIONS
  • CURLOPT_PINNEDPUBLICKEY
  • CURLOPT_POSTFIELDS
  • CURLOPT_PRIVATE
  • CURLOPT_PROXY
  • CURLOPT_PROXY_SERVICE_NAME
  • CURLOPT_PROXYUSERPWD
  • CURLOPT_RANDOM_FILE
  • CURLOPT_RANGE
  • CURLOPT_REFERER
  • CURLOPT_SERVICE_NAME
  • CURLOPT_SSH_HOST_PUBLIC_KEY_MD5
  • CURLOPT_SSH_PUBLIC_KEYFILE
  • CURLOPT_SSH_PRIVATE_KEYFILE
  • CURLOPT_SSL_CIPHER_LIST
  • CURLOPT_SSLCERT
  • CURLOPT_SSLCERTPASSWD
  • CURLOPT_SSLCERTTYPE
  • CURLOPT_SSLENGINE
  • CURLOPT_SSLENGINE_DEFAULT
  • CURLOPT_SSLKEY
  • CURLOPT_SSLKEYPASSWD
  • CURLOPT_SSLKEYTYPE
  • CURLOPT_UNIX_SOCKET_PATH
  • CURLOPT_URL
  • CURLOPT_USERAGENT
  • CURLOPT_USERNAME
  • CURLOPT_USERPWD
  • CURLOPT_XOAUTH2_BEARER
Aquellas cuyo valor debe ser una matriz:
  • CURLOPT_CONNECT_TO
  • CURLOPT_HTTP200ALIASES
  • CURLOPT_HTTPHEADER
  • CURLOPT_POSTQUOTE
  • CURLOPT_PROXYHEADER
  • CURLOPT_QUOTE
Aquellas cuyo valor debe ser un recurso de flujo de datos:
  • CURLOPT_FILE
  • CURLOPT_INFILE
  • CURLOPT_STDERR
  • CURLOPT_WRITEHEADER
Aquellas cuyo valor debe ser una función o una función de cierre:
  • CURLOPT_HEADERFUNCTION
  • CURLOPT_PASSWDFUNCTION
  • CURLOPT_PROGRESSFUNCTION
  • CURLOPT_READFUNCTION
  • CURLOPT_WRITEFUNCTION
Aquellas cuyo valor debe ser una muy específica:
  • CURLOPT_SHARE

Función curl_setopt_array.

Nos dimos cuenta como la función exit() mejoró muy bien la legilibilidad de nuestro código y además proporciona información a nuestros usuarios en caso de tratamiento de excepciones. Pero hay una manera aún mejor de configurar las opciones justo antes de ejecutar curl_exec() con el detalle que es como la famosa película de vaqueros: tiene lo bueno, lo malo y lo feo pero sin lo feo. Lo bueno -y muy bueno- es que como dijimos mejora la apariencia de nuestro código y abre nuevas posibilidades con el uso de una matriz: podemos realizar una función que consulte una base de datos y llene dicha matriz. Lo malo es que al pasar todo de un solo golpe a la unficón ésta acepta todo o no acepta nada y tendremos nuestra bella salida diciendo “no se pudo establecer culr_setopt_arraypero no nos indicará dónde, cuál es el elemento que falla.

<?php
  $objCurl = curl_init()
    or exit("No se pudo inicializar PHP curl.<br>");

  $verbose = fopen('verbose.txt', 'a')
    or exit("No se pudo abrir el archivo verbose.txt<br>");
  $opciones = array(
                    CURLOPT_URL     => 'https://icanhazip.com',
                    CURLOPT_VERBOSE => 1,
                    CURLOPT_STDERR  => $verbose
                    );

  curl_setopt_array($objCurl, $opciones)
    or exit("No se pudo establecer curl_setopt_array()");

  curl_exec ($objCurl)
    or exit("No se pudo ejecutar PHP curl.<br>");

  curl_close ($objCurl);
?>

Percataos que colocamos el URL en la matriz para abultar y activamos el modo de depuración de resto todo lo demás es el mismo ejemplo que colocamos anteriormente. Lo curioso del asunto es que curl_setopt() y curl_setopt_array() pueden trabajar juntos perfectamente, lo anterior lo podemos reescribir como lo próximo y funciona plenamente:

<?php
  $objCurl = curl_init()
    or exit("No se pudo inicializar PHP curl.<br>");

  curl_setopt($objCurl, CURLOPT_URL ,'https://icanhazip.com')
    or exit("No se pudo establecer el CULROPT_URL");


  $verbose = fopen('verbose.txt', 'a')
    or exit("No se pudo abrir el archivo verbose.txt<br>");
  $opciones = array(
                    CURLOPT_VERBOSE => 1,
                    CURLOPT_STDERR  => $verbose
                    );

  curl_setopt_array($objCurl, $opciones)
    or exit("No se pudo establecer curl_setopt_array()");

  curl_exec ($objCurl)
    or exit("No se pudo ejecutar PHP curl.<br>");

  curl_close ($objCurl);
?>

Analizando los resultados de la última conexión.

Comentamos que PHP curl puede utilizarse para fabricar una sencilla herramienta de monitoreo de servidores. Pensando en esto indagamos en ciertas opciones que trae la librería y de la cual podemos obtener gran cantidad de información, siempre pensando en guardarlo en una base de datos para su posterior análisis de manera masiva. Pero debemos comenzar por lo básico y no irnos precipitadamente.

Aparte de las opciones de depuración, que ya logramos vertirlas en un archivo que llamamos simplemente “verbose.txt” (y cuyo nombre podemos cambiar a la fecha y hora actual para que siempre sea un archivo distinto y almacenar -o borrar, o procesar, etc.- cuando queramos) podemos echar mano de otra función para obtener los datos de la conexión. Tal función es denominada curl_getinfo() a la que introducimos nuestro manejador creado por curl_init().

Esta función devuelve falso si no tiene información o una matriz con elementos predefinidos -no todos-. Para poder escribir esta matriz en un archivo agarramos a la función serialize() para convertirla en una cadena de texto antes de guardarla.

De una vez vamos a la práctica con el primer ejemplo que presentamos, el más “refinado” con las llamadas exit() ¿lo recordais? Pero vamos un paso adelante y lo modificamos, primero para que tome la URL por el método GET: escribimos en la barra de direcciones la ubicación de nuestro guion y al final agregamos un signo de cierre de interrogante con la palabra “url”, luego un signo de igualdad y a continuación el dominio o página que queremos descargar y guardar -para luego analizar según el propósito que tengamos, que para cada persona es muy variopinto. Supongamos que nuestro guion está en nuestro servidor local y se llama “curl.php”, el llamado sería el siguiente (obvien el prefijo “http://” porque lo agregamos en el script):

http://localhost/curl.php?url=www.ks7000.net.ve

También agregaremos dos mensajes adicionales: si no se pasa una URL entonces alerta que debe pasar una y otro mensaje para indicar que ha sido guardada en un archivo (recordad que usamos la opcion CURLOPT_RETURNTRANSFER al valor de 1). Coloreamos el código para su mayor comprensión:

<?php
if ($_GET["url"])
{
 $objCurl = curl_init("http://".$_GET["url"])
 or exit("No se pudo inicializar PHP curl.<br />");

 $nom_arch = 'servidor.html';
 $arch = fopen( $nom_arch , "a")
 or exit("No se pudo abrir el archivo '".$nom_arch."'<br />");

 curl_setopt ($objCurl, CURLOPT_RETURNTRANSFER, 1)
 or exit("No se pudo establecer CURLOPT_RETURNTRANSFER.<br />");

 curl_setopt ($objCurl, CURLOPT_FILE, $arch)
   or exit("No se pudo establecer CURLOPT_FILE.<br />");

 curl_setopt ($objCurl, CURLOPT_CONNECTTIMEOUT, 7)
   or exit("No se pudo establecer CURLOPT_CONNECTTIMEOUT.<br />");
 curl_exec ($objCurl)
 or exit("No se pudo ejecutar PHP curl.<br />");

 $nom_arch = "servidor_respuesta.txt";
 $archivo = fopen($nom_arch, "a")
 or exit("No se pudo abrir el archivo'".$nom_arch."'<br />");

 $resp = curl_getinfo($objCurl)
 or exit("No se pudo obtener información de la ultima conexion.<br />");

 fwrite($archivo, serialize($resp))
 or exit("No se pudo escribir en el archivo '".$nom_arch."'<br />");

 fclose($archivo)
 or exit("No se pudo cerrar el archivo '".$nom_arch."'<br />");

 // curl_close NO devuelve resultado:
 curl_close ($objCurl);
 echo "URL descargada y guardada.<br />";
} else {
 echo "Debe pasar una URL para ser descargada y analizada.<br />";
}
?>

Es crucial llamar esta función ANTES de llamar la función curl_close() que es cuando aún existe el objeto en memoria.

Matrix con campos predefinidos.

Esos datos que guardamos en el anterior software que inventamos ya están seleccionados por los desarrolladores de PHP curl y son los siguientes (repetimos, NO son todos los disponibles):

  • “url”
  • “content_type”
  • “http_code”
  • “header_size”
  • “request_size”
  • “filetime”
  • “ssl_verify_result”
  • “redirect_count”
  • “total_time”
  • “namelookup_time”
  • “connect_time”
  • “pretransfer_time”
  • “size_upload”
  • “size_download”
  • “speed_download”
  • “speed_upload”
  • “download_content_length”
  • “upload_content_length”
  • “starttransfer_time”
  • “redirect_time”
  • “certinfo”
  • “primary_ip”
  • “primary_port”
  • “local_ip”
  • “local_port”
  • “redirect_url”
  • “request_header” (solamente está definido si CURLINFO_HEADER_OUT está establecido por una llamada previa a curl_setopt())

Totalidad de los campos de get_info()

Podemos solicitar cualquiera de estos campos pasando como parámetro los siguientes valores a la función curl_getinfo() -con una breve descripción y ordenados alfabéticamente-:

  • CURLINFO_APPCONNECT_TIME – Tiempo, en segundos, desde el inicio hasta la conexión/apretón de manos SSL/SSH del host remoto.
  • CURLINFO_CERTINFO – Serie de certificados TLS.
  • CURLINFO_CONDITION_UNMET – Información del condicional de timepo unmet.
  • CURLINFO_CONNECT_TIME – Tiempo, en segundos, que tomó el establecimiento de la conexión.
  • CURLINFO_CONTENT_LENGTH_DOWNLOAD – Logitud del contenido de la descarga, leída desde el campo “Content-Length:”.
  • CURLINFO_CONTENT_LENGTH_UPLOAD – Tamaño especificado de subida .
  • CURLINFO_CONTENT_TYPE – “Content-Type:” del documento solicitado. NULL indica que el servidor no envío un encabezado “Content-Type:” válido.
  • CURLINFO_COOKIELIST – Todas las cookies conocidas.
  • CURLINFO_EFFECTIVE_URL – Último URL efectivo.
  • CURLINFO_FILETIME – Hora del documento remoto obtenido; si devuelve -1, la hora del documento es desconocida.
  • CURLINFO_FTP_ENTRY_PATH – Ruta de entrada en el servidor FTP.
  • CURLINFO_HEADER_OUT – El string de la petición enviada. Para que funcione, se ha de añadir la opción CURLINFO_HEADER_OUT al manejador, llamando a curl_setopt()
  • CURLINFO_HEADER_SIZE – Tamaño total de los encabezados recibidos.
  • CURLINFO_HTTP_CODE – Último código HTTP recibido.
  • CURLINFO_HTTP_CONNECTCODE – El código de respuesta de CONNECT.
  • CURLINFO_HTTPAUTH_AVAIL – Máscara de bits que indica el/los método/s de autenticación disponible/s de acuerdo a la respuesta anterior.
  • CURLINFO_LOCAL_IP – Dirección IP local (fuente) de la conexión más reciente.
  • CURLINFO_LOCAL_PORT – Puerto local (fuente) de la conexión más reciente.
  • CURLINFO_NAMELOOKUP_TIME – Tiempo, en segundos, en resolver el nombre.
  • CURLINFO_NUM_CONNECTS – Número de conexiones que curl ha tenido que crear para lograr la transferencia anterior.
  • CURLINFO_OS_ERRNO – Número de error (errno) de un fallo de conexion. Es número es específico de cada SO y sistema.
  • CURLINFO_PRETRANSFER_TIME – Tiempo, en segundos, desde el inicio hasta justo antes de que comience la transferencia del fichero.
  • CURLINFO_PRIMARY_IP – Dirección IP de la conexión más reciente.
  • CURLINFO_PRIMARY_PORT – Puerto de destino de la conexión más reciente.
  • CURLINFO_PRIVATE – Datos privados asociados a este manejador de cURL, previamente establecidos con la opción CURLOPT_PRIVATE de curl_setopt()
  • CURLINFO_PROXYAUTH_AVAIL – Máscara de bits que indica el/los método/s de autenticaición proxy disponible/s de acuerdo a la respuesta anterior.
  • CURLINFO_REDIRECT_COUNT – Número de redireccionamientos, con la opción CURLOPT_FOLLOWLOCATION habilitada.
  • CURLINFO_REDIRECT_TIME – Tiempo, en segundos, de todos los pasos de redireción antes de que la última transación haya empezado, con la opción CURLOPT_FOLLOWLOCATION habilitada.
  • CURLINFO_REDIRECT_URL – Con la opción CURLOPT_FOLLOWLOCATION inhabilitada: URL de redirección encontrado en la última transacción, que debería ser solicitado manualmente luego. Con la opción CURLOPT_FOLLOWLOCATION habilitada: está vacío. El URL de redirección en este caso está disponible en CURLINFO_EFFECTIVE_URL
  • CURLINFO_REQUEST_SIZE – Tamaño total de las peticiones realizadas, actualmente solo para peticiones HTTP.
  • CURLINFO_RESPONSE_CODE – El último código de respuesta.
  • CURLINFO_RTSP_CLIENT_CSEQ – Siguiente CSeq cliente de RTSP.
  • CURLINFO_RTSP_CSEQ_RECV – CSeq recivido recientemente.
  • CURLINFO_RTSP_SERVER_CSEQ – Siguiente CSeq servidor de RTSP.
  • CURLINFO_RTSP_SESSION_ID – ID de sesión de RTSP.
  • CURLINFO_SIZE_DOWNLOAD – Número total de bytes descargados.
  • CURLINFO_SIZE_UPLOAD – Número total de bytes subidos.
  • CURLINFO_SPEED_DOWNLOAD – Velocidad media de descarga.
  • CURLINFO_SPEED_UPLOAD – Velocidad media de subida.
  • CURLINFO_SSL_ENGINES – Criptomotores OpenSSL admitidos.
  • CURLINFO_SSL_VERIFYRESULT – Resultado de la verificación del certificado SSL solicitado por la opción CURLOPT_SSL_VERIFYPEER.
  • CURLINFO_STARTTRANSFER_TIME – Tiempo, en sengudos, hasta que el primer byte está a punto de transferirse.
  • CURLINFO_TOTAL_TIME – Duración total, en segundos, de última transferencia.

Resumen.

Recuerden lo que siempre dijo en vida nuestro Gran Maestro: “un punto, un punto y una línea recta“; aprovechen estos conocimientos para desarrollar programas y utilidades para fortalecer el Patrimonio Tecnológico de nuestra Humanidad no para escribir virus y gusanos que marguen las vidas de los demás.

<Eso es todo, por ahora>.

Fuentes consultadas.

En idioma castellano.

En idioma inglés.

PHP tutorial

Un lenguaje importante y que hemos dejado de lado por años es el ya muy famoso lenguaje PHP. En desagravio publicamos esta entrada y le queremos dar características especiales: no insisteremos en ser detallistas al extremo, se trata de una introducción, un tutorial que lleve paso por paso a cualquier persona que desee aprender dicho lenguaje de programación. Por ello no publicaremos su historia, configuración del servidor, etcétera. Os recordamos que para aprender PHP sería bueno tener conocimientos mínimos de HTML, CSS y JavaScript.

PHP de manera “sucia y rápida”.

No nos asustéis con la traducción al castellano del popular refrán en idioma inglés “quick and dirty”, que siempre se recuerda cuando se necesita desarrollar algún software que goce de pragmatismo al extremo. Lo que queremos decir es que cuando uno necesita resultados rápidos pues conviene saltarse los “paso a paso” que siempre usamos para aprender algo nuevo.

Elementos básicos en PHP.

El lenguaje PHP bien puede utilizarse en comandos por una venta terminal o bien lo podemos usar un proceso por lotes almacenados en un archivo de texto al cual le colocaremos una extensión “.php” para identificar de manera rápida su contenido. Siendo entonces un fichero en texto plano procedemos a escribir dentro de él lo siguiente:

<?php
  // aquí va el código
?>

Estas tres simples líneas le indican al servidor dónde comienza y termina las instrucciones que va a ejecutar en lenguaje PHP. Esto es así porque dentro de un archivo “.html” podremos incluir código especial que muy probablemente emita código en lenguaje HTML de manera dinámica (por ejemplo, una consulta en una base de datos para mostrar los valores seleccionados por el usuario). Las dos barras inclinadas “//” indica que todo lo que está a la derecha y hasta el final de la línea son comentarios y que no deben ni serán ejecutados. Tambien sirve “#” para comentar una línea, pero si necesitamos comentar varias líneas escribimos “/* comentario(s) */“.

Con excepción de las líneas de comentarios, inicio y cierre, toda las demás líneas deben terminar con un punto y coma “;“.

Variables en PHP.

Toda variable comienza con “$” y un caracter A-Z (mayúsculas y/o minúsculas) y/o el guión bajo “_“, luego se pueden usar números y/o caracteres sin dejar espacios. Se pueden clasificar en:

  • Número entero (positivos o negativos).
  • Número flotante (número decimal positivos o negativos).
  • Booleano (verdadero o falso).
  • Cadena de caracteres (las almacenamos con comillas simples).

Hay cuatro tipos más de variables avanzadas que veremos en su debida oportunidad. Una vez hallamos guardado un valor en una variable se pueden mostrar por pantalla por medio de las ordenes echo o print(), las cuales tiene muchas opciones que veremos más adelante pero detallaremos que no es lo mismo usar comillas simples que dobles con dichas funciones, mirad este ejemplo:

<?php
  miValor = 5;
  echo '<p>El valor es $miValor .</p>';
  echo "<p>El valor es $miValor .</p>";
?>

Y devolverá lo siguiente (código HTML recibido por el navegador, en Mozilla Firefox pulsad las teclas CTRL+U):

<p>El valor es $miValor.</p><p>El valor es 5.</p>

Así vemos que el comando echo bien puede “pasarle” al navegar el texto (en este caso texto + lenguaje HTML) o bien puede mediante instrucciones especiales “pasar” el valor almacenado en una variable. Notad que aunque los comandos en PHP es indistinto si los escribimos en mayúsculas y/o minúsculas las variables son sensibles: no es lo mismo $miValor que $MiValor, mucho cuidado con eso que causa bastantes dolores de cabeza a nosotros los programadores.

Otro uso en el campor de las variables, aunque poco utilizado, es declarar una segunda variable por referencia con otra variable. Para esto usamos el caracter “&” justo delante de la variable que queremos pasar. Ejemplo:

$a = "texto"; $b = &$a; echo $b;

Suma de variables en PHP.

Para sumar dos variables, ya sea para presentarla por pantalla o para almacenarla en otra variable, utilizamos los operadores matemáticos de costumbre, mirad:

<?php
 $a = 5; $b = 10; $c = $a + $b;
 echo "a + b =";
 echo $a + $b;
 echo "<br>c=";
 echo $c;
?>

Concatenar variables en PHP.

Decimos concatenar en el caso de variables de texto, en PHP con un simple punto unimos la variables:

<?php
 $a = "Inicio"; $b = " fin.";
 echo $a.$b;
?>

Declaración y alcance de las variables en PHP.

Como estudiamos hace poco, no es necesario decirle a PHP qué tipo de variable vamos a trabajar, simplemente ponemos los valores y el lenguaje se encarga de almacenarlo en el lugar adecuado. Cuando vayamos a realizar operaciones con dichas variables éstas se convertirán de a un valor correcto, por ejemplo:

<?php
 $a = "palabra"; $b = 5;
 echo $a + $b;
 echo "<br>";
 echo $a.$b;
?>

El resultado por pantalla será “5”, es decir, el valor de $a es una cadena de texto cuyo valor numérico es cero y así lo convierte para sumarlo a $b. Por el contrario, en la siguiente línea $a.$b estamos concatenando cadenas de texto y el valor de $b se convierte en el caracter “5” para mostrar lo siguiente:

5
palabra5

En PHP podemos “forzar” que una variable sea de un tipo específico que necesitemos, para ello usar el comando settype() con dos argumentos entre paréntesis separado por una coma: nombre de la variable y luego el tipo de variable (“integer”, “double”, “string”, “booelan”), ejemplo: «setttype( $a , “integer”)».

Otra manera de forzar a un tipo de variable es de la siguiente manera: «$variable = (int) $variable;» en este caso (int) o (integer) indica que lo cambie a tipo entero pero podemos usar también (real), (double), (float), (boolean) o (bool).

Otra cosa es el alcance de las variables: si están en el módulo principal las variables serán globales y si están dentro de una función serán locales y solamente podrán ser accesibles dentro de la función donde fueron “creadas”. Si necesitamos que una variable dentro de una función esté disponible para otras funciones (y en el módulo principal) usaremos la palabra global y a continuación le asignaremos el valor; de facto estamos declarando la variable al asignarle un valor.

Cuando declaramos una variable como global ésta se adiciona a la matriz $GLOBALS[‘nombre_de_variable’] y podemos acceder a ellas de esta manera nemotécnica, especialmente es útil para usarla dentro de funciones; ejemplo: «echo $GLOBAL[‘cadena’]». Aunque aún falta mucho para ver las funciones, podemos adelantar el siguiente código que ilustra el alcance de las variables locales y globales:

<?php
 $variable1 = 'ABC';
 $variable2 = 123;
 function mi_funcion(){
  $variable1='perro';
  echo $variable1.'<br>';
  echo $GLOBALS["variable1"].'<br>';
  echo $GLOBALS["variable2"].'<br>';
 }
 mi_funcion();
?>

Si necesitamos que una variable dentro de una función conserve su valor entre llamadas, debemos declararla con el comando static dentro de la función a la que pertenece y asignarle un valor, por supuesto.

Incrementos y decrementos en variable numéricas.

Como es popular en lenguaje C++ los incrementos se logran colocándole “++” (o “–” si es un decremento) luego del nombre de la variable. En este ejemplo ambas líneas son equivalentes pero en una escribimos más que en la otra:

<?php
 $a = 10;
 $a = 10 + 1;
 echo $a."<br>";

 $a = 10;
 $a++;
 echo $a."<br>";
?>

Pero fijaos que al mostrarlo en pantalla, cuando el incremento (o decremento) lo colocamos ANTES o DESPUÉS de la variable se comporta de manera diferente, cambia el orden de como se ejecutan las instrucciones:

<?php
 $a = 10;
 echo "Incremento posterior:<br>";
 echo $a++."<br>";
 echo $a."<br>";
$a = 10;
 echo "Incremento anterior:<br>";
 echo ++$a."<br>";
 echo $a."<br>";
?>

Operaciones artiméticas en PHP.

Aparte de las cuatro operaciones básicas (suma “+”, resta “-“, multiplicación “*” y división “/”) debemos señalar también al módulo “%” (devuelve el residuo de la división, si es cero la división es exacta) y la exponenciación “**”.

Otros operadores importantes son la suma y asignación “+=”, resta y asignación “-=”, multiplicación y asignación “*=”, división y asignación “/=”, módulo y asignación “%=” y concatenación y asignación “.=”. Esencialmente lo que hace es aplicar el operador aritmético al valor ya almacenado en la variable con el otro valor a la derecha del signo de igualdad. El siguiente código ilustrará mejor:

<?php
 $x=10;$x+=5;echo $x."<br>"; # resultado 15
 $x=10;$x-=5;echo $x."<br>"; # resultado 5
 $x=10;$x*=5;echo $x."<br>"; # resultado 50
 $x=10;$x/=5;echo $x."<br>"; # resultado 2
 $x=10;$x%=5;echo $x."<br>"; # resultado 0
?>

Operadores de comparación.

Necesitamos oepradores que permitan comparar dos variables, pero en PHP hay ciertas condiciones a evaluar. Para empezar ya sabemos que las variables puede almacenar valor numérico y en la siguiente línea le podemos almacenar una cadena de texto, es por ello que necesitamos comparar, incluso, si son el mismo tipo de variable. Por ejemplo, para nosotros 5 es igual que 5,00 paro para PHP el primer valor es un número entero positivo y el segundo es un valor decimal positivo (nosotros en este sitio web nuestro separador decimal es la coma pero a nivel de lenguajes de computación el separador decimal es el punto). Veamos los que disponemos:

  • ==“: Comprueba si son iguales.
  • !=“: Comprueba si son distintos.
  • ===“: Comprueba si son iguales y de exactamente el mismo tipo.
  • !==: Comprueba si son distintos o de distinto tipo.
  • <>: Diferente (igual que !=).
  • <: Menor que, comprueba si un valor es menor que otro.
  • >: Mayor que.
  • <=: Menor o igual que.
  • >=: Mayor o igual que.
  • <=>: Comparador de orden (PHP 7).
  • ??: uno o el otro (PHP 7).

Trabajando con variables de texto.

PHP contiene comandos (funciones no declaradas por nosotros, funciones implícitas) que nos facilitan grandemente nuestro trabajo con cadenas de texto, ya vimos echo y print() veamos otras más.

Contando el número de caracteres.

Usaremos el comando strlen() pasando como argumento nuestra variable (osea colocamos nuestra variable dentro de los paréntesis del comando). Por ejemplo, el siguiente código devolverá el valor de treinta y cinco letras:

<?php
  $cadena = "República Bolivariana de Venezuela";
  echo strlen($cadena);
?>

Pero el problema aquí es que no son 35 caracteres sino 34: la letra u acentuado es contada como 2 caracteres porque se necesitan 2 bytes para representarla. En realidad esta función devuelve el número de bytes, no el número de caracteres. Ya vemos que nuestro hermoso idioma castellano, normado por nuestro brillante Don Andrés Bello, nos da y seguirá dando trabajo a nosotros los programadores.

Contando el número de palabras en una cadena de texto.

Teniendo en cuenta que una palabra esta delimitada antes y después por uno o más espacios y esté constituida por caracteres A~Z y/o a~z, el comando que usaremos es str_word_count(). Siguiendo con el ejemplo anterior:

<?php
  $cadena = "República Bolivariana de Venezuela";
  echo str_word_count($cadena);
?>

Y el resultado será simplemente cinco ¿Qué sucedió si sólo hay cuatro palabras? Pues lo mismo de siempre: los lenguajes de programación están escritos por gente de habla inglesa para los cuales los acentos, la letra eñe -y demás caracteres no pertenecientes al idioma inglés simplemente no existen. Pero esperen, aún hay más: los números tampoco cuentan como caracteres así que por lo menos no nos podemos quejar de que ellos allá en el departamente de programación de PHP no son estrictos con ellos mismos. Veremos de forma ampliada que este comando acepta un segundo parámetro llamado format que permite el resutlado de manera distinta: si es cero es el valor por defecto, uno devuelve una matriz conteniendo cada una de las palabras y dos devuelve la posición numérica de cada una de las palabras, veamos:

<?php

  $cadena = "República      Bolivariana
       de     Venezuela";
  echo "<br>Palabras encontradas:";
  echo str_word_count($cadena);
  echo "<br><br>Valor FORMAT por defecto cero:<br>";
  print_r(str_word_count($cadena, 0));
  echo "<br><br>Matriz con cada una de las palabras:<br>";
  print_r(str_word_count($cadena, 1));
  echo "<br><br>Matriz con la ubicación de cada una de las palabras:<br>";
  print_r(str_word_count($cadena, 2));

?>

Lo cual devuelve lo siguiente:

Palabras encontradas:5

Valor FORMAT por defecto cero:
5

Matriz con cada una de las palabras:
Array ( [0] => Rep [1] => blica [2] => Bolivariana [3] => de [4] => Venezuela ) 

Matriz con la ubicación de cada una de las palabras:
Array ( [0] => Rep [5] => blica [16] => Bolivariana [35] => de [42] => Venezuela )

Fijaos que la cadena de texto hemos introducido más espacios adicionales, la hemos escrito en dos líneas pero PHP la interpreta como una sola ya que al final de la segunda línea es que se encuentra el punto y coma que indica el final. También ya sabéis que la letra u acentuada cuenta como dos caracteres (ya que esa característica del juego de caracteres UTF-8 permite “ampliar” el número de bytes para representar los más de 65 mil caracteres que componen el UNICODE) y es por ello que “blica” comienza en 5 y no en 4.

Pensando ellos allá en lo anterior, a partir de la versión PHP 5.1.0 se le agregó un tercer argumento que permite especificar una excepción a los caracteres que nosotros consideramos no delimita una palabra. Esto conlleva definir un valor constante a lo largo de todo nuestro programa por medio del comando define() que toma dos argumentos separados por una coma: primero el nombre de la variable en sí y segundo el conjunto de caracteres que consideramos NO delimita una palabra y encerrados entre comillas, mirad el siguiente ejemplo:

<?php

  define( Castellano, 'áéíóúÁÉÍÓÚüÜñÑ');

  $cadena = "República Bolivariana de Venezuela.
En Güigüe se siembra mucho el ñame y el limón.";
  echo "<br><br>Valor FORMAT por defecto cero:<br>";
  print_r(str_word_count($cadena, 0, Castellano));
  echo "<br><br>Matriz con cada una de las palabras:<br>";
  print_r(str_word_count($cadena, 1, Castellano));
  echo "<br><br>Matriz con la ubicación de cada una de las palabras:<br>";
  print_r(str_word_count($cadena, 2, Castellano));

?>

Importante hacer notar que los valores constantes NO necesitan el signo “$” delante: ya PHP sabe que es un valor “especial” a tomar en cuenta. La función include() acepta un tercer parámetro: podemos especificar que el nombre de la variable NO sea sensible a mayúsculas y minúsculas. Esto quiere decir que si colocamos true en el tercer argumento, podremos llamara a nuestra constante a lo largo del programa como “Castellano” o “CASTELLANO” o “cASTELLano”, etc. Toda constante declarada es de tipo GLOBAL. A partir de PHP 7 acepta valores de matrices, solo como nota informativa.

Cadenas reversadas.

Si queremos encontrar palíndromos esta función está adecuada para ello: strrev() devuelve una cadena de texto al revés, un ejemplo en idioma latín:

<?php
 // Latín: "Damos vueltas en la noche y somos consumidos por el fuego"
 echo "in girum imus nocte et consumimur igni<br>";
 echo strrev("in girum imus nocte et consumimur igni");
?>

Encontrar una subcadena en una cadena de texto.

Función bastante usada: necesitamos saber si en una cadena de texto existe una subcadena: strpos() devolverá un valor numérico, cero o mayor si consigue la subcadena, de lo contrario devolverá un valor falso. Debemos tener en cuenta que falso=cero y cualquier otro valor es verdadero, como esta función puede devolver cualquier valor entonces debemos evaluar con el triple signo de igualdad para estar seguros que no se consiguió la subcadena. Ejemplo:

<?php
 $cad=strpos("Ministerio del Poder Popular para la Comunicación e Información.", "Comunicación"); //
 if ($cad===false){
 echo "falso";
 }else{
 echo $cad;
 }
?>

Sustituir una subcadena de texto.

Dada una cadena de texto necesitamos sustituir, si se consigue dentro de ésta, por otra cadena de texto: str_replace() hará el trabajo si le colocamos como primer argumento la subcadena a buscar, el segundoa rgumento la cadena que sustituirá -de ser encontrada- y la cadena de texto en donde se buscará. Ejemplo:

<?php
 $cad=str_replace("Sulia", "Zulia", "Maracaibo, estado Sulia.");
 echo $cad;
?>

Extraer una subcadena de texto.

Muchas veces necesitamos extraer un subcadena de texto a partir de su posición numérica hasta el fin de la cadena de texto: substr() con tres argumentos: cadena de texto, comienzo y longitud. Si se omite la longitud pues devolverá hasta el final de la cadena de texto. Si el número de comienzo es negativo devolverá la cadena contando como primer caracter el primero de la derecha, osea el final. Si no consigue la cadena devolverá un valor falso o una cadena de texto vacía.

<?php
 # Muestra "para no serlo"
 echo substr("Llamarse jefe para no serlo es el colmo de las desgracias.",14,13);
?>

Instrucciones condicionales.

Si condicional.

Para evaluar una variable y un valor o dos variables (o dos valores, algo menos común) usaremos el si condicional. En PHP tiene la siguiente estructura:

if (condición a evaluar) {instrucciones a ejecutar}

if (condición a evaluar) {
  instrucciones a ejecutar;
}

De la segunda manera resulta más ordenada y estructurada pues permite multilíneas comodamente. Si necesitamos ejecutar instrucciones diferentes según la condición verdadera o falsa:

if (condición a evaluar) {
  instrucciones a ejecutar;
} else {
  instrucciones a ejecutar;
}

Lo coloreado en verde se ejecuta si la condición es verdadera y lo coloreada en morado de ser falso. Cuando necesitamos evaluar varias condiciones, “filtro de múltiples tamices” utilizaremos esta estructura:

if (condición a evaluar) {
  instrucciones a ejecutar;
} elseif (condición a evaluar) {
 instrucciones a ejecutar;
} else {
 instrucciones a ejecutar;
}

Con el siguiente ejemplo ilustraremos mejor la estructura: con la función date() obtenemos la hora y fecha del equipo pero si le pasamos la máscara “H” nos devuelve solamente la parte de la hora y en formato militar (hora de 24 horas); se pasa por un tamiz dicho resultado y según la hora saluda de manera adecuada:

<?php
  $hora = date("H");
  if ($hora < "12") {
   echo "Buenos días.";
 } elseif ($hora < "19") {
   echo "Buenas tardes.";
 } else {
   echo "Buenas noches.";
 }
?>

Como vemos con tres condiciones revista ya cierta complejidad por lo que es necesario un operador más poderoso: el comando switch().

Interruptores condicionales.

Acá debemos abrir un poco la mente: muchas veces necesitamos comparar una variable contra diferentes valores para ejecutar una o más instrucciones e incluso necesitamos que dicha variable sea evaluada más de una vez. Para ello existe el comando switch() que evalua exactamente cada valor y si coincide exactamente ejecuta la(s) instrucción(es) y sale del ciclo con un comando break():

<?php
$i=0;
switch ($i) {
    case 0:
        echo "i vale 0<br>"; break;
    case 1:
        echo "i vale 1<br>"; break;
    case 2:
        echo "i vale 2<br>"; break;
    default:
      echo "i es mayor a 2<br>";
}
?>

El ejemplo se establece el valor en cero y solo se ejecuta la primera instrucción y sale del ciclo. Cualquier valor que NO sea 0, 1 ó 2 imprimirá “que i es mayor a 2” incluso si $i tiene un valor negativ0. Es por ello que este comando debe ser ejecutado con precaución. Otro detalle a tomar en cuenta es que si queremos ejecutar varia líneas de instrucciones debemos encerrarlos entre corchetes.

Comandos de ciclo.

Ciclo mientras “while”.

Con el ciclo while() si se cumple la condición evaluada se ejecutarán una y otra vez cada una de las instrucciones contenidas y se espera que dentro del ciclo hayan eventos que cambien el valor y poder salir del bucle, sino decimos que el ordenador “está colgado”, así que mucho cuidado con esto último:

while (condición evaluada) {
     instrucciones a ser ejecutados;
 }

Como ejemplo utilizaremos la variable k con un valor inicial de 1 y lo incrementaremos de dos en dos hasta que supere a diez:

<?php
  $k = 1;

  while($k < 10) {
    echo "Valor de <i>k</i>: $k <br>";
    $k += 2;
}
?>

Ciclo hacer mientras “do … while”.

El ciclo mientras “while” evalúa la condición y de ser cierta ejecuta el bloque de instrucciones deseado en cambio el ciclo hacer-mientras entra al ciclo, ejecuta y luego evalúa la condición: si se cumple vuelve a ejecutar de lo contrario sale del ciclo y continúa el flujo del programa. Para ilustrar bien este punto colocamos el siguiente ejemplo muy a propósito para demostrarlo:

<?php
  $k = 10;
  do {
    echo "The number is: $x <br>";
    $k++;
  } while ($k <= 5);
?>

Este código mostrará “El valor de k es: 10″ que sabemos que es mayor que 5 sin embargo el bloque de instrucciones se ejecuta al menos en una oportunidad.

Ciclo para “for”.

El anterior hacer-mientras se hace más fácil de ejecutar con el ciclo para el cual tiene tres parámetros: el que inicializa la variable, la condición que evalua la variable y el incremento en cada ciclo que le vamos a aplicar a la variable:

for (inicia_contador; evalua-contador; incrementa_contador) {
  código_a_ejecutar;
}

Con valores numéricos puede ser más “fácil” de comprender:

<?php
  For ($fuente=1; $fuente<=7;$fuente++) {
    echo "<font size=$fuente>Tamaño de fuente $fuente</font><br>";
  }
?>

Ciclo para cada “for each”.

Este comando se utiliza con matrices, se estudiará más adelante cuando veamos este tipo de objetos.

Funciones personalizadas.

Ya tocamos el tema de bastantes “comandos” o funciones implícitas en PHP: no es necesario declararlas, ya están implementadas y aunque podemos hacer bastante con el trabajo hecho de manera previa esto no es suficiente para desarrollar nuestros programas. Por esta razón PHP (y la mayoría de los lenguajes de programación) permiten hacer nuestras propias funciones, aquellas que esperamos usar de manera repetitiva en nuestros programas.

function nombre_de_funcion(argumento) {
}

Las funciones NO son sensibles a minúsculas y/o mayúsculas: no importa como la escribamos ellas son llamadas y ejecutadas. Con valores:

<?php
  function muestra_mensaje() {
    echo "Ejemplo de funciones de PHP.";
  }

Muestra_Mensaje(); // Hace el llamado a la función.
?>

Acá comprobamos que no importa como llamemos la función, ésta es ejecutada. Por ahora a esta función no le pasamos ningún argumento pero a continuación la modificamos y dentro de ella con el valor que le pasamos podemos trabajar y procesarlo para mostrarlo o devolver algún resultado en memoria:

<?php
  function muestra_mensaje($mensaje) {
    echo "Ejemplo de funciones de PHP.<br>";
    echo $mensaje;
  }
Muestra_Mensaje("¡Aprendiendo a programar en PHP!"); 
?>

También podemos pasar un argumento con un valor por defecto, dado el caso no le pasemos un valor:

<?php
  function muestra_mensaje($mensaje = "Ejemplo de funciones de PHP.<br>") {
    echo $mensaje;
  }
Muestra_Mensaje();
Muestra_Mensaje("¡Aprendiendo a programar en PHP!");
?>

Por último con la orden return variable podremos beneficiarnos del uso más poderoso de las funciones: devolver valores:

<?php
  function suma($a, $b) {
    $total = $a + $b;
    return $total;
  }

echo "5 + 10 = " . suma(5, 10) . "<br>";
echo "7 + 13 = " . suma(7, 13) . "<br>";
echo "21 + 4 = " . suma(21, 4);
?>

Fuentes consultadas.

En idioma inglés:

Image Magick

ImageMagick (tutorial).

Recientemente tuvimos la estupenda oportunidad de asistir al Congreso de Tecnologías Libres 2016 y tuvimos la necesidad (madre de las invenciones) de publicar las fotografías que capturamos en el evento. En un principio redimensionamos unas pocas para nuestra cuenta Twitter, pero pronto nos dimos cuenta que la tarea es tediosa y debemos aligerar la carga con herramientas del Software Libre. No hace mucho tiempo uno de nuestros faros en GNU/LINUX -en lengua castellana- Ubunlog publicaron un artículo sobre ImageMagick: instalación y usos básicos del mismo. Pero como nos percatamos que el proceso masivo  de 300 imágenes en una sola linea de comando puede “colgar” nuestra computadora por largo tiempo decidimos publicar esta entrada con el valor agregado de nuestros anteriores temas publicados y además unos “scripts” que tal vez les puedan ser útiles a ustedes, amén de la recomendación de un “plugin” para WordPress con el cual escribimos estas líneas a la fecha (quien sabe, tal vez algún día evolucionemos hacia otra plataforma de blogging).

ImageMagick.

ImageMagick
ImageMagick

Breve historia.

Bien lo retrata en su página web la historia de ImageMagick que pasamos a traducir y resumir, contada en idioma inglés por John Cristy (Principal ImageMagick Architect):

Corría el año de 1987 cuando el Dr. David Pensak, supervisor de John Cristy en la empresa de productos químicos llamada Dupont, le solicitó  poder mostrar imágenes de 24 bits (color verdadero) en los nuevos -y costosos- monitores de 256 colores ya que hardware de aquella época tenía muy poca potencia -y por ende debían ser convertidos a 256 colores-. Es por ello que John Cristy utilizó el buscador de moda para ese entonces: Usenet. Obtuvo respuesta de Paul Ravelin donde le indicaba no una, sino varias soluciones de software para la tarea encomendada y puso a su disposición un servidor FTP del “Information Sciences Institute” (ente adscrito a la Universidad del Sur de California) con el código fuente de numerosas aplicaciones. Tras varios años de conseguir muchas de las respuestas, en lo que a computación se refiere, en su trabajo para la empresa Dupont -y el exigente Dr. David Pensak- él se decidió a mejorar y retribuir todo el software utilizado y decidió igualmente liberar las herramientas de procesamiento de imágenes para que otros -¡ejem! nosotros por ejemplo- nos beneficiriamos de ello (de hecho nosotros contribuimos en esta entrada con un “script” en “bash” y otro en lenguaje PHP, así que la historia ¡sigue y sigue!).

Pero como del “dicho al hecho hay enorme trecho” él primero tenía que solicitar permiso a la empresa Dupont en la cual laboraba, ya que en horas de trabajo fue que él desarrolló dichas herramientas. Es así que de nuevo interviene el Dr. David Pensak y convence a sus superiores de otorgar permiso de “copyleft” a John Cristy ya que no era ni un producto químico ni biológico y ellos no tenían noción del valor del software para entonces. Es así que el 1° de agosto de 1990 ImageMagick ve la luz en Usenet en el grupo “comp.archives” (gracias de nuevo Dr. Pensak).

A mediados de los años 1990, y con miles de usuarios en el mundo entero, ImageMagick versión 4.2.9 fue incluido en un nuevo sistema operativo que era distribuido libremente: GNU/Linux.

Es así que luego de su distribución junto a GNU/Linux el sr. Bob Friesenhahn contacta a John Cristy a fin de “normalizar” la aplicación para que sea compatible con el resto de las herramientas de dicho sistema operativo (más adelante veremos que gracias a esto es que hoy en 2016 nosotros podemos desarrollar “scripts” o guiones funcionales y compatibles en otros idiomas de programación).

A partir de la versión 5 de ImageMagick se incorpora de esta manera el lenguaje C++ y se unen al desarrollo los siguientes programadores:

Ya eran decenas de miles de usuarios de ImageMagick cuando sucedió lo impensable: el desarrollo evolucionó tanto que en un momento dado la nueva versión era incompatible con una API existente e hizo que los usuarios reaccionaran bruscamente y exigieron paralizar la programación mientras que los desarrolladores quería seguir adelante. John Cristy no dio su brazo a torcer así que ImageMagick -de la mano de Bob- recibe su primera bifurcación de código y nace Magick++, el primer “fork” (como se conoce en el idioma inglés). Recordemos que precisamente esta es una de las normas de la licencia que rige el software libre, así que John Cristy continuó solo su camino.

Pero no trabajó solo por mucho tiempo: Anthony Thyssen le indicó ciertas fallas en la linea de comandos, los cuales no solo se corrigieron sino que también se mejoraron hasta tal punto que vieron que era necesario emitir una nueva versión: ImageMagick 6.0.

Tan lejos llegaron las librerías de Anthony Thyssen que el mismo John Cristy quedó sorprendido de la capacidad del código fuente original, y que públicamente reconoce la labor hecha en el avance de la colaboración en proyectos de software libre. A continuación, y en honor de quienes contribuyeron (y respetando las normas de la licencia GNU bajo la cual está concebida ImageMagick) nombramos a:

  • Fred Weinhaus (cientos de “scripts” que son libres para uso no comercial, caso contrario contactar a Fred Weinhaus para su autorización).
  • Glenn Randers-Pehrson (gurú del formato PNG).
  • Dirk Lemstra (desarrollo en ambiente “Windows” bajo .NET)

ImageMagick tiene ya una edad de 25 años al momento de escribir este artículo, y rumbo a los siguientes 25 años se desarrolló la versión 7.0 con importantes novedades descritas en este enlace web. Además, ustedes pueden encontrar la licencia que rige a ImageMagick en este otro enlace.

Instalación de ImageMagick en Ubuntu.

La instalación es común a las distros GNU/Linux basados en Debian:

apt-get install imagemagick

Recuerden que deben tener derechos de usuario raíz, para mayores detalles al trabajar la línea de comandos consulten nuestro tutorial al respecto.

Finalmente, para verificar si está correctamente instalado en nuestro ordenador, podemos ejecutar las siguientes lineas de comando con las cuales “crearemos” el logotipo de ImageMagick, visualizaremos sus especificaciones con el comando identify y luego lo abriremos en una ventana gráfica con el comando display:

convert logo: logo.gif
identify logo.gif
display logo.gif

Al ejecutar el comando display tal vez recibiréis un mensaje un tanto singular: el reporte de unas fuentes de texto faltantes. La explicación rápida es que son fuentes privativas, no libres, y no acompañan a las distribuciones GNU/Linux. Más información en este enlace web.

Tal vez, cuando estéis más avezado o avezada con ImageMagick, necesitareís instalar las librearías avanzadas (una de tantas que existen) con el siguiente comando:

apt-get install graphicsmagick-imagemagick-compat

Como vosotros podéis ver, de primero utilizamos el comando convert el cual pasamos a describir en la siguiente sección.

Comando “convert”.

El comando que nos interesa para redimensionar de manera masiva -y a nuestra manera- una gran cantidad de imágenes es el comando “convert“. Específicamente para redimensionar lo acompañamos del argumento “-resize” y de seguido los dos valores de ancho y alto deseados. Sin embargo, debemos conocer un poco más acerca de algunos de los otros argumentos disponibles:

  • Lo más básico: renombrar imágenes de manera masiva seleccionando un patrón de búsqueda y un prefijo que automáticamente numerará el comando. Por ejemplo si introducimos la orden “convert *.jpg fotos.jpg” ImageMagick renombrará todos los archivos jpg en la carpeta donde estemos ubicados en la linea de comandos de la siguiente manera: foto-1.jpg , foto-2.jpg , foto-3.jpg , etc.
  • Ya vimos cómo renombrar masivamente un grupo de imágenes pero para convertir una sola solo debemos, desde luego, indicarle su nombre específico, y si queremos o necesitamos, otro nombre específico de salida para mantener el original; es decir, si omitimos el segundo nombre ImageMagick reemplazará el archivo de imagen original -cuidado con esto-. Las siguientes opciones soportan ambas maneras en este párrafo descritas y renombran masivamente según el párrafo anterior.
  • Para rotar una imagen utilizamos el argumento “-rotate” seguido del ángulo a rotar, por ejemplo “convert imagen.jpg -rotate 90 nueva_imagen_rotada.jpg“.
  • Si queremos convertir a otro formato de archivo simplemente especificamos el o los archivos deseados acompañado del nombre con la extensión deseada. Por ejemplo “convert imagen.jpg imagen.png” o si queremos convertir todas las imágenes jpg en una carpeta: “convert *.jpg imagen.png” (recordad que ImageMagick agregará un sufijo numerado a cada archivo convertido: imagen-1.png , imagen-2.png , imagen-3.png , etc.)
  • También podemos bajarle calidad a una imagen utilizando el argumento “-quality” acompañado del porcentaje deseado -formato jpg-.
  • Si necesitamos redimensionar utilizamos, por ejemplo, “convert imagen.jpg -resize 1024×768” con lo cual obtendremos una imagen de tamaño 1024 píxeles de ancho por 768 píxeles de alto sin conservar el archivo original. Para obtener un archivo nuevo (otro ejemplo) emplearíamos “convert imagen.jpg -resize 1024×768 imagen_redimensionada.jpg“.
  • Por último podemos combinar los diferentes argumentos, teniendo en cuenta el problema con el que nosotros nos tropezamos: el redimensionamiento masivo de imágenes puede hacer que nuestro ordenador quede bloqueado durante un buen tiempo, por eso decidimos utilizar un “script” que procesa uno a uno cada archivo.

Uso de ImageMagick en un “bash script”.

Ya en una entrada anterior hablamos procesar una serie de imágenes y aplicarle Reconocimiento óptico de caracteres con el programa Tesseract y vamos a reutilizar el “script bash” o proceso por lotes allí muy bien explicado, así que si os gusta id, leedlo y volved.

#!/bin/sh
####Licencia de uso###
# Copyright 2016 Jimmy Olano at ks7000.net.ve
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################
patron="P*.JPG"
nomarch="lista.txt"
clear
ls $patron > $nomarch
while read linea
do
 echo "Procesando "$linea
 convert $linea -resize 1024x768 $linea
 echo "aplicando logotipo a "$linea
 php funde_logo.php $linea
 rm $linea
done <$nomarch
rm $nomarch
echo "Trabajo terminado, imagenes redimensionadas e insertadas con logotipo."

Como veís publicamos de una buena vez el script y pasamos a describirlo linea por linea para su rápida comprensión:

  • En la línea N° 1 especificamos que es un archivo de procesos por lotes.
  • De la línea 2 a la 16 le establecemos la licencia Apache la cual es adecuada para pequeños proyectos y así garantizamos legalmente que nuestro trabajo se podrá seguir compartiendo y ampliando con el tiempo y se impedirá la creación de patentes hechas a base del mismo.
  • En la línea 17, entre paréntesis, colocamos el patrón de archivos a buscar para redimensionar. Nosotros utilizamos “P*.JPG” por nuestra cámara marca Panasonic que le agrega ese prefijo seguido de una numeración única y consecutiva.
  • Línea 18: asignamos el nombre del archivo donde guardaremos los nombres de los archivos a redimensionar. Nota: de hecho sabemos que hay una estructura alterna más eficiente para procesar archivos: “for file in *.png; do comando; done”, la idea de utilizar un archivo auxiliar es para, a futuro, poder llevar auditoría o registro de los archivos modificados. Es así que podemos, por ejemplo, cambiar el nombre de “lista.txt” a “archivos_redimensionados_dia_mes_año.log” por la extensión utilizada en inglés para el verbo “to log” o registrar o llevar un registro.
  • En la línea 19 limpiamos la consola para legibilidad en la ejecución de la tarea.
  • En la línea 20 buscamos los archivos que cumplan con el patrón de búsqueda de la línea 17 y guardamos sus nombres -para posterior uso- en el archivo nombrado de la línea 18.
  • De la línea 22 a la 28 se encuentra el ciclo o rutina principal deseado. En la línea 20 borramos -a menos que debamos hacer auditoría- (ver explicación de la línea 18). Con la línea 21 notificamos al usuario que la tarea ha finalizado (más sin embargo no llevamos una variable lógica -verdadero o falso- con los resultados de cada uno de los comando ejectuados, es susceptible de ser emjorado). A continuación mostramos las líneas debidamentes numeradas, trabajo hecho y alojado cortesía de Github:

Ahora pasamos a describir la rutina principal:

  • En la línea 21 comenzamos a leer el archivo “lista.txt” el cual contiene los nombres de los archivos con los cuales trabajaremos.
  • Entre la línea 21 y 28 establecemos el ciclo que se ejecutará hasta que hallamos leído de manera secuencial todos y cada uno de los nombres almacenados.
  • En la línea 23 le indicamos al usuari sobre cual archivo vamos procesando.
  • Línea 24: aquí es donde utilizaremos a imageMagick con la variable “$linea” que contiene el archivo que redimensionaremos (ver línea 23). Usamos el argumento “-resize” para obtener una imagen de 1024 por 768 píxeles reemplazando el archivo original.
  • Línea 26: llamamos a un “script” o guión en lenguaje PHP. Lo explicaremos en la sección siguiente, pero os adelanto que de allí obtendremos un archivo totalmente nuevo.
  • Línea 27: borramos el archivo original a fin de ahorrar en espacio en disco (no, no importa si ya tenemos discos duros con decenas de terabytes: cuando montamos un servidor web que reciba decenas de miles o incluso millones de visitas el ahorro de espacio en disco en muy importante).

Uso de PHP en un guión o archivo de proceso por lotes.

Aunque a la fecha no hemos escrito un tutorial sobre lenguaje PHP podemos adelantar que es un lenguaje de proceso por lotes de lado del servidor lo cual lo convierte en una poderosa herramienta para realizar páginas web de manera dinámica e interactiva con el usuario.

Ya vimos cómo con el lenguaje de marcación HTML5 podemos “escribir” o hacer páginas web pero limitadas a presentar siempre el mismo aspecto, y para cambiarlo debemos tomar el archivo, editarlo y guardarlo para, por ejemplo, agregar una nueva imagen o texto a nuestra web. Con el lenguaje PHP podremos, mediante un guión -con extensión .php- insertar comandos que responden a variables para, por ejemplo, mostrar diferentes logotipos según el tamaño de pantalla del dispositivo con la cual visitan nuestra página e incluso conectarnos a una base de datos para extraer texto, imágenes o cualquier otra información allí almacenada y así “personalizar” nuestro portal web. Es por ello que se habla de “páginas web estáticas” y “páginas web dinámicas”: con PHP obtenemos HTML según lo que necesitemos exhibir de acuerdo a variables de tiempo o valores específicos.

De hecho, el lenguaje con que funciona este blog está escrito en PHP y al conjunto de guiones -o procesos por lote- se denomina WordPress y estas líneas están guardadas en una base de datos MySQL. Hay muchísimos tutoriales sobre lenguaje PHP que podéis buscar con DuckDuckGo así que no profundizaremos demasiado en esta presentación pero es necesario que para continuar nuestra enseñanza visitéis, leed y comprended nuestra entrada sobre creación de imágenes CAPTCHA ya que las librerías que utilizaremos son las mismas. Y no os preocupéis, no vamos a montar un servidor web en el estricto sentido de la palabra, pero si usaremos elementos que se usan de manera común en ellos pero con la novedad de que lo ejecutaremos con la línea de comandos.

Línea de comandos en PHP.

En el sitio web oficial de PHP se describe detalladamente el uso de archivos PHP en una ventana terminal (linea de comandos). Allí detallan que hay tres maneras de ejecutar archivos con contenido PHP (no necesariamente con extensión “.php”) desde la línea de comandos:

  1. Decirle a PHP que ejecute un archivo específico, por ejemplo «php archivo.php».
  2. Decirle a PHP que ejecute lo que a continuación se escribe, siempre colocandolo entre comillas simples, por ejemplo «php -r ‘$algo=4; print_r($algo);’».
  3. Concatenar comandos “bash” con el símbolo de tubería “|”, eso en GNU/Linux es llamado standard input (stdin), por ejemplo «php -r ‘phpinfo();’ | grep “GD”»

En la tercera opción colocamos un ejemplo para conocer si tenemos instaladas las librerías GD necesarias para nuestro caso: insertar un logo en todos de cada una de las imágenes que deseamos redimensionar. Al ejecutarlo podremos ver algo como esto:

php -r 'phpinfo();' | grep "GD"
php -r ‘phpinfo();’ | grep “GD”

Buscamos que “GD Support” esté habilitado, “enabled”; caso contrario debemos instalarlo con la siguiente orden:

sudo apt-get install php5-gd

Asimismo se indica que se le pueden pasar argumentos dados al ejecutar un guión PHP invocandolos dentro del guión con el comando “$argv[]”. Se debe colocar entre corchetes el número de argumento en el mismo orden que se escribe en la línea de comandos, haciendo la salvedad que $argv[0] siempre será el nombre del archivo que contiene las instrucciones en lenguaje PHP.

Veamos unos sencillos ejemplos:

  • Creamos un archivo php con el siguiente contenido:
<?php
  print_r($argv[0])
?>
  • Luego llamamos al script con la siguiente orden «php archivo.php»
  • Por pantalla veremos algo parecido a esto:
php archivo.php
php archivo.php

Explicación de nuestro bash en PHP.

Como ya estudiamos de manera resumida y rápida el uso de la línea de comandos con guiones PHP, a continuación mostramos de manera numerada -cortesía de Github- el archivo de proceso por lotes “funde_logo.php“:

Y describimos línea por línea su funcionamiento:

  • En la línea 1 declaramos que usaremos el lenguaje PHP entre esta línea y la línea 46 (para este caso todo el archivo).
  • De la línea 2 a la 27 insertamos las licencias de uso, son dos porque la primera aplica al guión en sí, su código fuente, y la segunda para indicar que estamos utilizando código escrito en PHP que de por sí tiene su propia licencia de uso.
  • En las líneas 28~30 insertamos un comentario sobre lo que realizaremos.
  • En la línea 31 cargamos el archivo que recibimos desde el primera argumento externo (argumento externo) al guión mediante el comando imagecreatefromjpeg en la variable $destino .
  • En las líneas 32~33 insertamos comentarios adicionales.
  • En la línea 34 cargamos el archivo de imagen (que contiene el logotipo deseado en la imagen redimensionada creada  con anterioridad fuera del guión PHP) en la variable $origen.
  • Líneas 35 y 36 más comentarios.
  • En la línea 37 se hace el trabajo principal: insertamos el logotipo en la imagen con el comando imagecopymerge. Este comando merece una explicación detallada a continuación.
  • El comando imagecopymerge tiene los siguientes argumentos:
    1. El nombre del archivo que le insertaremos el logotipo.
    2. El nombre del archivo del logotipo.
    3. La coordenada X donde insertaremos el logotipo en la imagen.
    4. La coordenada Y donde insertaremos el logotipo en la imagen.
    5. La coordenada X del logotipo en si mismo.
    6. La coordenada Y del logotipo en si mismo.
    7. La anchura deseada del logotipo.
    8. La altura deseada del logotipo.
    9. El porcentaje de transparencia del logotipo: 100 es completamente opaco, no se mostrará nada el archivo original en el logotipo insertado
  • Líneas 38 a 40: comentarios.
  • Línea 41: preparamos lo que tenemos en memoria para volcarlo al disco duro con el comando imagejpeg con los argumentos siguientes: la variable $destino (a la cual le insertamos el logotipo), el nombre con el que queremos guardar el archivo (un prefijo llamado “CTL2016-” junto al nombre del archivo original pasado por el argumento externo al script PHP) y, por último, el nivel de calidad deseado, en este caso un 80%.
  • Líneas 42, 43, comentarios.
  • Líneas 44 y 45 liberamos la memoria donde almacenamos las imágenes.

Debemos aclarar que para saber las coordenadas donde insertaremos el logotipo, pues simplemente abrimos una de las fotografías tomadas con el software Pinta, luego abrimos el logotipo con el mismo programa en otra ventana, seleccionamos todo y copiamos, volvemos a la primera venta y pegamos y arrastramos a la posición deseada y tomamos nota de las coordenadas, hágase según arte como dicen en farmacia.

En cuanto a las coordenadas del logotipo pues sencillamente es TODO: de [0, 0] hasta [370, 150] : el ancho y alto total del mismo.

El comando “mogrify”.

Uno de nuestros “faros” en el mundo específico de Ubuntu es la página web Ubunlog.com quienes publicaron un mini tutorial con otro comando de ImageMagick: el comando mogrify.

Podéis ir a ese sitio web y leer (y aprender) sobre ese otro comando que nos permite redimensionar de manera masiva nuestras imágenes. Un detalle que notamos con este comando es que podemos redimensionar a cualquier tamaño específico pero si queremos -o necesitamos- que la imagen se ajuste (encoja y alargue, según sea el caso) debemos acompañar del signo de cierre de admiración para denotar esto. Es una ligera diferencia, he aquí un ejemplo de cada uno de los dos comandos:

mogrify -resize 800x200! prueba.jpg

El comando redimensionará exactamente a 800 píxeles de ancho por 200 píxeles de alto y la imagen se expandirá (en nuestro caso nuestra imagen original era de 3504×2332 píxeles) en ambas direcciones, por lo que se verá deformada.

mogrify -resize 800x200 prueba.jpg

En este caso NO utilizamos el signo de cierre de admiración (con la misma imagen de 3504×2332 píxeles) y al ejecutar el comando anterior nos produce una imagen de 201×200 píxeles lo cual respeta la relación ancho contra altura de la imagen original.

Converseen: interfaz gráfica basada en ImageMagick.

Como ImageMagick es Software Libre y está escrito en lenguaje C, el equipo de programación de fasterland.net en la persona de Francesco Mondello, sacaron partido de esto y desarrollaron una interfaz gráfica para otros sistemas operativos, con ustedes un bonito vídeo de presentación de Converseen:

En sus propias palabras:

Converseen, thanks to ImageMagick, the powerful image manipulation library on which the program leans it’s basis, can supports more than 100 image formats including DPX, EXR, GIF, JPEG, JPEG-2000, PhotoCD, PNG, Postscript, SVG, TIFF and many others.
Converseen is very easy to use, it’s designed to be fast, practical and, overall, you can get it for free!

Converseen incluso va más allá y le da valor agregado al permitir convertir un archivo en formato pdf en una serie de imágenes (por ahora desconocemos si las utilerías para manejar archivos en formato pertenecen a ImageMagick y por eso decimos que es un valor agregado):

Como podéis observar, Converseen PERMITE redimensionar de manera masiva las imágenes de una o varias carpetas e incluso ofrece una vista previa, prefijo para nombres de archivos (y ubicarlos en otra carpeta) y mucho más, ¡es como una navaja suiza!

Por último en esta sección, os dejamos el vídeo tutorial sobre cómo instalarlo en Ubuntu y el enlace hacia la propia página web del autor donde publica una entrada al respecto:

A nosotros en lo particular nos encanta la línea de comandos, donde para instalar Converseen solo debemos introducir estas dos sentencias (puede tardar algo de tiempo, dependiendo de su velocidad de descarga de internet):

sudo apt-get update

sudo apt-get install converseen

sudo apt-get install converseen
sudo apt-get install converseen

ImageMagick y la seguridad informática.

El Señor Pablo González, Ingeniero Informático, autor de numerosos libros en España y asesor de seguridad de reconocidas empresas radicadas en ese reino, publicó el jueves cinco de mayo de dosmildieciséis un artículo titulado “Command injection en ImageMagick: Actualiza todos tus servers GNU/Linux o te podrían hackear con una imagen” donde indica un fallo de seguridad en ImageMagick que permitiría ejecutar comandos bash con el nivel de usuario que hayamos colocado en nuestro servidor GNU/Linux.

Aunque esta parte es algo más avanzada para este humilde tutorial, dado lo delicado del asunto procedemos a explicarlo “en cristiano” o en castellano llano para que lo tengáis presente y trataremos de explicarlo lo más simple posible.

Como mencionamos ImageMagick está ampliamente extendido del mundo Unix de donde nació y tal como lo relatamos lo incorporaron a Linux y otros sistemas operativos. Pero bajo linux la potencia de ImageMagick se elevó a nivel tal que se usa mucho en servidores web con ayuda de otros lenguajes tales como (no limitados y/o combinados) HTML, PHP, PYTHON, etc.

He aquí que hay páginas web que ofrecen herramientas de edición y/o creación de imágenes a los usuarios. De hecho nosotros aprovechamos el código existente para crear CAPTCHAS para distintas páginas web de nuestros clientes (de nuevo, humildemente desarrolladas). En este caso es bastante seguro la creación de imágenes con PHP y sus librerías pero con ImageMagick hasta podemos permitir que nuestros usuarios web “suban” imágenes a nuestro servidor y allí es donde radica el problema.

Si un atacante malicioso -o no- subiera una imágen con código embebido meticulosamente manipulado para tal efecto al aplicarle el comando convert que estudiamos permite ejecutar el comando “infectado” en la línea de comandos.

El caso está ampliamente documentado en el siguiente enlace, pero no pudimos reproducir el comportamiento del fallo esperado porque regularmente mantenemos actualizados nuestros equipos a los repositorios oficiales.

Enlaces relacionados.

Enlaces en idioma castellano:

Enlaces en idioma inglés: