martes, 11 de noviembre de 2014

Obtener los links que vienen en el contenido de una URL usando cURL de PHP

En el anterior post hemos explicado cómo podemos obtener el contenido de una URL usando la librería cURL de PHP.

En esta ocasión, a partir de lo que nos devuelva dicha función, y mediante el objeto DOMDocument, recorreremos el contenido y buscaremos todos los enlaces que existan.

Lo primero que necesitaremos será la función con la que obtener el contenido de la URL:

function getUrlContent($url){
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
 curl_setopt($ch, CURLOPT_TIMEOUT, 5);
 $data = curl_exec($ch);
 $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 curl_close($ch);
 return ($httpcode>=200 && $httpcode<300) ? $data : false;
}


A continuación desarrollamos la función que recorre el contenido y guarda en un array las direcciones de todos los enlaces que se encuentre:
function getUrls($url){
 // obtenemos el contenido de la url
 $content = getUrlContent($url);

 if ($content==false){
  return false;
 }

 // creamos el array de resultados
 $urls = array();

 // creamos una instanacia a DOMDocument
 $doc = new DOMDocument;
 $doc->preserveWhiteSpace = false;
 
 // cargamos la pagina web desdecargada desde la función getUrlContent()
 @$doc->loadHTML($content);

 // buscamos todos los tags del tipo < a >
 $links = $doc->getElementsByTagName("a");
 
 // recorremos cada uno de los tags encontrados
 foreach ($links as $link){
  // obtenemos la parte href de la etiqueta
  $href = $link->getAttribute("href");
  
  if (strlen($href)>=4 && substr($href,0,4)=="http"){
   // url entera
   $urls[] = $href;
  } else if (strlen($href)>0 && substr($href, 0, 1)=="/"){
   // url empieza por / le añadimos el dominio por delante
   $urls[] = $url.$href;
  }else{
   // arhivo
   $urls[] = $url.'/'.$href;
  }
 }

 // eliminamos duplicados
 $urls = array_unique($urls);
 
 // ordenamos alfabéticamente
 sort($urls);
 
 return $urls;
}
Una vez hecho esto ya solamente nos quedará la llamada a la función y el mostrar en pantalla el resultado:
// especificamos la url
$url = "http://softontherocks.blogspot.com.es";

// obtenemos las url
$urls = getUrls($url);

// mostramos los resultados
if ($urls==false){
 echo "La página no es correcta, o es una redirección.";
} else {
 echo nl2br(implode($urls, "\n"));
}

Descargar el contenido de una URL usando cURL de PHP

Descargar el contenido de una URL es algo que puede ser bastante común para un programador web. Con PHP y la librería cURL (Client URL) podemos conseguirlo con una función bastante simple.

La librería cURL suele venir activa en la configuración de la mayoría de hosting que hay en el mercado. Además, para activarla, si somos nosotros los que configuramos PHP, simplemente debemos descomentar la línea que viene en el php.ini que hace referencia a php_curl.

La función en cuestión es la siguiente:

function getUrlContent($url){
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
 curl_setopt($ch, CURLOPT_TIMEOUT, 5);
 $data = curl_exec($ch);
 $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 curl_close($ch);
 return ($httpcode>=200 && $httpcode<300) ? $data : false;
}


Función para escribir en un fichero log con PHP

Es habitual que cuando realizamos cualquier aplicación queramos guardar un log en fichero de texto plano de ciertas funciones o partes de nuestro código, bien sea para solucionar posibles errores, detectarlos o cualquier otra funcionalidad que le queramos aplicar. En nuestro caso lo usamos para llevar un registro de las consultas SQL que lanzamos sobre la base de datos y ver posibles problemas.

Desde hace tiempo utilizo esta sencilla función que os dejo aquí:

function writeLog($data) {
 list($usec, $sec) = explode(' ', microtime());
 $datetime = strftime("%Y%m%d %H:%M:%S",time());
 $msg = "$datetime'". sprintf("%06s",intval($usec*1000000)).": $data";
 $save_path = 'foo.txt';
 $fp = @fopen($save_path, 'a'); // open or create the file for writing and append info
 fputs($fp, "$msg\n"); // write the data in the opened file
 fclose($fp); // close the file
}

Después, la llamada a esta función se haría de una forma muy simple:

writeLog('Este es el primer log');

Y el resultado que nos aparecería en el fichero foo.txt sería este:

20141111 07:26:14'914062: Este es el primer log


viernes, 7 de noviembre de 2014

Función json_last_error y json_last_error_msg de PHP

En el anterior post vimos el funcionamiento de la función json_decode() que decodificaba una cadena JSON y la convertía a una variable PHP. Ahí provocamos un error estableciendo una profundidad de la recursividad menor de la que tenía el JSON.

Con las funciones json_last_error() y json_last_error_msg() obtenemos el error, si se produce, que ha ocurrido en la última codificación o decodificación JSON mediante las funciones json_encode() o json_decode().

La primera de las funciones, json_last_error() devuelve un valor entero con el ID del último error que se haya producido. Esta función está disponible a partir de PHP 5.3.0.

En cambio, json_last_error_msg() está disponible únicamente a partir de PHP 5.5.0 y devuelve un string con el error de la última llamada a json_encode() o a json_decode().

Ejemplo

Con este ejemplo lo que haremos será provocar el mismo error que en el anterior post y obtener el resultado. El ejemplo lo vamos a ejecutar en un PHP 5.3.29, con lo que la función json_last_error_msg() no estará disponible, con lo que nos devolverá el resultado la función que creemos nosotros:

$json = '{"users":[{ "user": "Carlos", "age": 30, "country": "Spain" }, { "user": "John", "age": 25, "country": "United States" }]}';

// Si existe la función json_last_error_msg devuelve su valor. En caso contrario si existe json_last_error devuelve el texto que tenemos asociado. Si usamos una versión anterior a PHP 5.3 nos devuelve el mensaje de que no se puede mostrar el error
function getJSONError(){
 return (function_exists('json_last_error_msg')) ? json_last_error_msg() : ((function_exists('json_last_error')) ? getJSONErrorMsg(json_last_error()) : 'This PHP cannot show the error');
}

// A partir del código de error nos muestra un mensaje
function getJSONErrorMsg($code){
 switch ($code) {
  case JSON_ERROR_NONE:
   $msg = 'No errors';
  break;
  case JSON_ERROR_DEPTH:
   $msg = 'Maximum stack depth exceeded';
  break;
  case JSON_ERROR_STATE_MISMATCH:
   $msg = 'Underflow or modes mismatch';
  break;
  case JSON_ERROR_CTRL_CHAR:
   $msg = 'Unexpected control character found';
  break;
  case JSON_ERROR_SYNTAX:
   $msg = 'Syntax error, malformed JSON';
  break;
  case JSON_ERROR_UTF8:
   $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
  break;
  default:
   $msg = 'Unknown error';
  break;
 }
 return $code.' '.$msg;
}

// Limitamos la profundidad de la recursividad a 2
$obj = json_decode($json, true, 2);
var_dump($obj);
/* devuelve
NULL -> la profundidad es 4
*/

if ($obj==null){
 $error = getJSONError();
 echo $error;
 /* devuelve
 1 Maximum stack depth exceeded
 */
}

Función json_decode de PHP

La función json_decode() convierte un string codificado en JSON a una variable de PHP apropiada. Si la cadena no puede ser decodificada o se alcanza el límite de profundidad de la recursividad, devuelve un NULL. En caso de error, se puede usar la función json_last_error() para conocer el motivo exacto.

Sintaxis

mixed json_decode ( string $json [, bool $assoc = false [, int $depth = 512 [, int $options = 0 ]]] )

Parámetros

json: cadena JSON que queremos decodificar. 

assoc: si lo ponemos a TRUE indicaremos que queremos convertir el objeto resultado en un array asociativo

depth: límite de profundidad de la recursividad. Por defecto está a 512. Si se supera el límite devolverá un error.

options: se trata de una máscara de bits de opciones de la decodificación. Actualmente está disponible únicamente JSON_BIGINT_AS_STRING que convertiría los enteros grandes a cadena, en lugar de a float como lo haría por defecto.

Ejemplo

Veamos con un ejemplo su funcionalidad y lo que devuelve en cada caso. Los nombres y valores de la cadena JSON siempre deben ir entre comillas dobles:


$json = '{"users":[{ "user": "Carlos", "age": 30, "country": "Spain" }, { "user": "John", "age": 25, "country": "United States" }]}';

// Decodificamos la cadena JSON
$obj = json_decode($json);
var_dump($obj);
/* devuelve
object(stdClass)#1 (1) { ["users"]=> array(2) { [0]=> object(stdClass)#2 (3) { ["user"]=> string(6) "Carlos" ["age"]=> int(30) ["country"]=> string(5) "Spain" } [1]=> object(stdClass)#3 (3) { ["user"]=> string(4) "John" ["age"]=> int(25) ["country"]=> string(13) "United States" } } }
*/

// Devolvemos el resultado como array
$obj = json_decode($json, true);
var_dump($obj);
/* devuelve
array(1) { ["users"]=> array(2) { [0]=> array(3) { ["user"]=> string(6) "Carlos" ["age"]=> int(30) ["country"]=> string(5) "Spain" } [1]=> array(3) { ["user"]=> string(4) "John" ["age"]=> int(25) ["country"]=> string(13) "United States" } } }
*/

// Limitamos la profundidad de la recursividad a 2
$obj = json_decode($json, true, 2);
var_dump($obj);
/* devuelve
NULL -> la profundidad es 4
*/


martes, 4 de noviembre de 2014

Buscar múltiples textos en un texto con PHP

A la hora de buscar si un texto aparece dentro de un texto más amplio mediante PHP tenemos una función muy conocida llamada strpos() que nos devuelve la posición de dicho texto en el otro.

Con la función estándar, si queremos buscar varios textos deberíamos repetir la llamada tantas veces como necesitemos. 

Para facilitar la tarea tenemos esta función en la que la lista de cadenas a buscar la pasaremos mediante un array y además le podremos decir si es una búsqueda sensible a mayúsculas/minúsculas o no.

La función es la siguiente:


function getMultiPos($haystack, $needles, $sensitive=true, $offset=0){
    foreach($needles as $needle) {
        $result[$needle] = ($sensitive) ? strpos($haystack, $needle, $offset) : stripos($haystack, $needle, $offset);
    }
    return $result;
}

La llamada a la función sería como veremos a continuación. La primera llamada es sensible a mayúsculas/minúsculas y la segunda no:


$haystack = "A cat in gloves catches no mice.";
$needles = array("glove", "mouse", ".", "cat", "No");

var_dump(getMultiPos($haystack, $needles));
/* output:
array(5) { ["glove"]=> int(9) ["mouse"]=> bool(false) ["."]=> int(31) ["cat"]=> int(2) ["No"]=> bool(false) }
*/

var_dump(getMultiPos($haystack, $needles, false));
/* output:
array(5) { ["glove"]=> int(9) ["mouse"]=> bool(false) ["."]=> int(31) ["cat"]=> int(2) ["No"]=> int(24) }
*/