Crear gráficas en PHP con Googchart

La librería GoogChart de Google puede descargarse libremente (licencia MIT) desde este enlace y nos permite generar gráficas desde PHP.

El primer paso es añadir la clase GoogChart a nuestro script de PHP tal que así:

include "GoogChart.class.php"; 

Ahora necesitamos varias cosas: Primero generar un objeto de la clase GoogChart, después un array con los colores que tendrán las columnas y luego otro array con los datos a mostrar.

$chart = new GoogChart(); //objeto
$color = array('#333','#666','#999','#ccc'); //colores
$datos = array(enero=>50, febrero=>35, marzo=>89, abril=>45);//datos simples

Con estos datos ya podemos darle valores a nuestro objeto e imprimirlo:

$chart->setChartAttrs( array(
'type' => 'bar-vertical',
'title' => 'Ventas 2012',
'data' => $datos,
'size' => array( 600, 300 ),
'color' => $color,
'labelsXY' => true
));

echo $chart;

A la hora de dar valores al objeto definimos el tipo de gráfica (en este caso barras verticales), el título, los datos, el tamaño (en píxeles), la paleta de colores y si tendrá etiquetas en los ejes. Ahora veamos un ejemplo con datos de varios años:

$datosMultiple = array(
 'Año 2011' => array(
 enero => 30,
 febrero => 20,
 marzo => 45,
 abril => 75
),
 'Año 2012' => array(
 enero => 50,
 febrero => 35,
 marzo => 89,
 abril => 65
 ),
 );

Este array incluye dos arrays de datos donde los nombres sirven como leyenda para la gráfica. El resto del proceso sería igual.

En fin, ahora lo que os queda es miraros los ejemplos que vienen junto al archivo de la clase y experimentar.

Leyendo ficheros CSV con PHP

A petición de Jorge de Saliceta, que lo reclamó en la entrada sobre lectura de ficheros en PHP va la explicación sobre cómo leer ficheros CSV con PHP. No es la primera vez que en este blog hablamos sobre el formato CSV (comma separated values).

Bueno, si te vas a la entrada de lectura de ficheros verás que para la lectura secuencial, línea a línea, usábamos la función fgets(). Bueno, pues existe una función similar, llamada fgetcsv(), pensada para trabajar con archivos de este tipo. En lugar de devolver una cadena de texto como fgets(), esta función lo que devuelve por cada línea recorrida es un array, donde cada valor separado por una coma ocupa una de las posiciones del mismo. Veamos un ejemplo:

//supongamos la siguiente fila en un csv:
//Manolo, Rodríguez, Málaga, Fontanero
//y que está guardado en el archivo prueba.csv
//Aplicaríamos el siguiente código

<?php
$file = fopen("prueba.csv","r");
$result = fgetcsv($file);
fclose($file);
?> 

//esto devolvería un array con el siguiente resultado:
Array
(
[0] => Manolo
[1] => Rodríguez
[2] => Málaga
[3] => Fontanero
) 

Cuestiones a tener en cuenta hay varias. La primera es que si pilla una línea en blanco devolverá una matriz con un sólo campo con el valor NULL. La segunda es que existen varios parámetros que podemos pasar: Obligatorio, desde PHP5, sólo es el archivo a parsear, pero también acepta la longitud máxima de línea (si la sabes pásalo, porque hará que la función vaya más rápido), el delimitador de campo (por defecto, la coma), el «cercado» de campo (por defecto comillas dobles… ya se que lo de cercado no es muy claro, en inglés sería enclosure) y el caracter de escape (por defecto, la barra invertida). Al igual que con fgets() puede haber problemas para detectar los finales de fila en archivos creados en un Mac, y también hay que tener en cuenta que la función tiene en cuenta la configuración local, así que si estás trabajando con UTF8 tenlo en cuenta a la hora de calcular el tamaño máximo de línea o con los archivos codificados en one-byte.

Finalmente os dejo un gran ejemplo del funcionamiento de esta función sacado de la web de php:


<?php
$fila = 1;
if (($gestor = fopen("test.csv", "r")) !== FALSE) {
    while (($datos = fgetcsv($gestor, 1000, ",")) !== FALSE) {
        $numero = count($datos);
        echo "<p> $numero de campos en la línea $fila: <br /></p>\n";
        $fila++;
        for ($c=0; $c < $numero; $c++) {
            echo $datos[$c] . "<br />\n";
        }
    }
    fclose($gestor);
}
?>

Leer ficheros de texto con PHP

Uno de esos posts a petición de un lector: ¿cómo leer un fichero de texto con PHP? Bueno, a día de hoy no es una práctica muy usada, tal vez sea algo que suena más a tiempos pasados, pero hace año y pico en un trabajo por ejemplo tuve que hacerlo (y no veáis la lata que daba tratar los datos cuando una tabla de MySQL habría ahorrado muchísimo trabajo, pero eso es otra historia).

Bueno, para leer un fichero debemos usar la función fopen(), pasándole como primer parámetro la dirección del fichero a abrir y como segundo el modo (para lectura r, para escritura w).

Entonces, para leer fopen(«archivo.txt», «r»), y ahora tenemos dos opciones: cargar todo el contenido de golpe o ir leyendo línea a línea.

Para cargar todo el contenido de golpe la cosa sería más o menos así:

function leer_completo($direccion_fichero){
   $fichero = fopen ($direccion_fichero, "r");   
   $contenido = fread($fichero, filesize($direccion_fichero));
   return $contenido;
} 

Esto devolvería una variable con todo el contenido del fichero. Para la lectura línea a línea en cambio necesitaremos un bucle y la función fgets() en lugar de fread().


<?php
$gestor = @fopen("/tmp/inputfile.txt", "r");
if ($gestor) {
    while (($búfer = fgets($gestor, 4096)) !== false) {
        echo $búfer;
    }
    if (!feof($gestor)) {
        echo "Error: fallo inesperado de fgets()\n";
    }
    fclose($gestor);
}
?>

En este caso ya os pego el ejemplo que aparece en la web oficial de php. En ese caso se especifica a fgets() el parámetro del tamaño a leer. Y es que puedes especificarle el tamaño de bloque que debe hacer en cada lectura o, por el contrario, no pasarle ningún parámetro y que lea hasta el final de línea. Esto último puede dar problemas con archivos creados en un Mac, pero se puede solventar activando la opcion en tiempo de ejecución auto_detect_line_endings.

Otro día, si queréis, hablamos de csv, o de escritura… estoy abierto a sugerencias.

Crear un chat con PHP y jQuery

En este caso no se trata de un artículo original, sino de una medio traducción medio interpretación de este original en inglés. Podéis descargaros todo el código desde el enlace.

Se trata de un chat web simple, programado en php y basado en Ajax, haciendo uso de jQuery, con función de login y logout y soporte para varios usuarios.

El tutorial comienza creando un archivo index.php tal cual este:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
<title>Chat - Customer Module</title>  
<link type="text/css" rel="stylesheet" href="style.css" />  
</head>  
<div id="wrapper">  
    <div id="menu">  
        <p class="welcome">Welcome, <b></b></p>  
        <p class="logout"><a id="exit" href="#">Exit Chat</a></p>  
        <div style="clear:both"></div>  
    </div>  
    <div id="chatbox"></div>  
    <form name="message" action="">  
        <input name="usermsg" type="text" id="usermsg" size="63" />  
        <input name="submitmsg" type="submit"  id="submitmsg" value="Send" />  
    </form>  
</div>  
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.0.js"></script>  
<script type="text/javascript">  
// jQuery Document  
$(document).ready(function(){  
});  
</script>  
</body>  
</html>

Como podéis ver se trata de un marcado HTML normal. La referencia a jQuery del código original está anticuada, en este ejemplo ya veréis una apuntando una dirección actualizada. En cuanto a la estructura hay tres grandes divs: el #wrapper, que contiene los otros dos y el formulario de envío de mensaje; el #menu, que sólo es un mensaje de bienvenida y un botón de logout y, finalmente, el #chatbox, que es donde se incluirán los mensajes.

El css sería tal que así:

    /* CSS Document */  
    body {  
        font:12px arial;  
        color: #222;  
        text-align:center;  
        padding:35px; }  
    form, p, span {  
        margin:0;  
        padding:0; }  
    input { font:12px arial; }  
    a {  
        color:#0000FF;  
        text-decoration:none; }  
        a:hover { text-decoration:underline; }  
    #wrapper, #loginform {  
        margin:0 auto;  
        padding-bottom:25px;  
        background:#EBF4FB;  
        width:504px;  
        border:1px solid #ACD8F0; }  
    #loginform { padding-top:18px; }  
        #loginform p { margin: 5px; }  
    #chatbox {  
        text-align:left;  
        margin:0 auto;  
        margin-bottom:25px;  
        padding:10px;  
        background:#fff;  
        height:270px;  
        width:430px;  
        border:1px solid #ACD8F0;  
        overflow:auto; }  
    #usermsg {  
        width:395px;  
        border:1px solid #ACD8F0; }  
    #submit { width: 60px; }  
    .error { color: #ff0000; }  
    #menu { padding:12.5px 25px 12.5px 25px; }  
    .welcome { float:left; }  
    .logout { float:rightright; }  
    .msgln { margin:0 0 2px 0; }  

Poca cosa que comentar, pero entre esto y el marcado ya tenemos la apariencia definida del chat.

Ahora toca el formulario de login en PHP:

    <?  
    session_start();  
    function loginForm(){  
        echo' 
        <div id="loginform"> 
        <form action="index.php" method="post"> 
            <p>Please enter your name to continue:</p> 
            <label for="name">Name:</label> 
            <input type="text" name="name" id="name" /> 
            <input type="submit" name="enter" id="enter" value="Enter" /> 
        </form> 
        </div> 
        ';  
    }  
    if(isset($_POST['enter'])){  
        if($_POST['name'] != ""){  
            $_SESSION['name'] = stripslashes(htmlspecialchars($_POST['name']));  
        }  
        else{  
            echo '<span class="error">Please type in a name</span>';  
        }  
    }  
    ?>     

En este caso se trata de un login simple, no comprueba usuarios registrados en una base de datos. Sobre su funcionamiento hay poco que comentar: nos pide un nombre y lo incluye dentro de una variable de sesión (de ahí la llamada a session_start()). El uso de htmlspecialchars() es para evitar ataques XSS.

Lo siguiente es motrar tanto el nombre de usuario en el Welcome como el formulario si el usuario no está logueado. La idea es dejar el primer archivo, el index.php tal cual este:

    <?php  
    if(!isset($_SESSION['name'])){  
        loginForm();  
    }  
    else{  
    ?>  
    <div id="wrapper">  
        <div id="menu">  
            <p class="welcome">Welcome, <b><?php echo $_SESSION['name']; ?></b></p>  
            <p class="logout"><a id="exit" href="#">Exit Chat</a></p>  
            <div style="clear:both"></div>  
        </div>  
        <div id="chatbox"></div>  
        <form name="message" action="">  
            <input name="usermsg" type="text" id="usermsg" size="63" />  
            <input name="submitmsg" type="submit"  id="submitmsg" value="Send" />  
        </form>  
    </div>  
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>  
    <script type="text/javascript">  
    // jQuery Document  
    $(document).ready(function(){  
    });  
    </script>  
    <?php  
    }  
    ?>  

Bien, ahora vamos con el código de Javascript para la funcionalidad de logout. Dentro del script que ya tenemos creado en nestro código insertamos:

    <script type="text/javascript">  
    // jQuery Document  
    $(document).ready(function(){  
        //If user wants to end session  
        $("#exit").click(function(){  
            var exit = confirm("Are you sure you want to end the session?");  
            if(exit==true){window.location = 'index.php?logout=true';}  
        });  
    });  
    </script>  

Esto no sólo nos permite enviar una llamada por el método GET para desconectar al usuario, sino que también nos sacará una ventana de confirmación antes de hacerlo. Y claro está, tendremos que tocar nuestro código del index.php para comprobar que se ha hecho una petición de logout y destruir la sesión que se había creado en el login:

    if(isset($_GET['logout'])){  
        //Simple exit message  
        $fp = fopen("log.html", 'a');  
        fwrite($fp, "<div class='msgln'><i>User ". $_SESSION['name'] ." has left the chat session.</i><br></div>");  
        fclose($fp);  
        session_destroy();  
        header("Location: index.php"); //Redirect the user  
    }  

Este código, además, escribe en un archivo de logs (más tarde vemos para qué se usará) que el usuario se ha desconectado y redirecciona de nuevo a la página principal.

Lo siguiente es manejar las inserciones de mensajes de los usuarios. Hay que empezar por definir una función de jQuery para enviar los mensajes por el método POST haciendo uso de Ajax. El proceso sería recoger el evento de click del formulario, leer los datos del campo de texto, enviarlos al script de php que los va a tratar y borrar el campo de texto para que quede de nuevo en blanco:

    //If user submits the form  
    $("#submitmsg").click(function(){  
        var clientmsg = $("#usermsg").val();  
        $.post("post.php", {text: clientmsg});  
        $("#usermsg").attr("value", "");  
        return false;  
    });  

Y claro, esto implica crear el archivo post.php para manejar los datos en el lado del servidor. Un archivo que llevaría este código:

    <?  
    session_start();  
    if(isset($_SESSION['name'])){  
        $text = $_POST['text'];  
        $fp = fopen("log.html", 'a');  
        fwrite($fp, "<div class='msgln'>(".date("g:i A").") <b>".$_SESSION['name']."</b>: ".stripslashes(htmlspecialchars($text))."<br></div>");  
        fclose($fp);  
    }  
    ?>  

De nuevo iniciamos sesión, comprobamos que el usuario está logueado, recogemos el texto del mensaje del array $_POST y escribimos el mensaje junto al nombre de usuario en el log del chat.

Finalmente llevamos a la parte de mostrar los mensajes de chat en pantalla. Lo primero es cargar el log del chat dentro del #chatbox si este existe, para ahorrarnos tiempo:

    <div id="chatbox"><?php  
    if(file_exists("log.html") && filesize("log.html") > 0){  
        $handle = fopen("log.html", "r");  
        $contents = fread($handle, filesize("log.html"));  
        fclose($handle);  
        echo $contents;  
    }  
    ?></div>  

Lo siguiente es la carga por Ajax de los datos contenidos en el archivo de log. La cosa se arreglaría con el método de jQuery para Ajax:

    //Load the file containing the chat log  
        function loadLog(){  
            $.ajax({  
                url: "log.html",  
                cache: false,  
                success: function(html){  
                    $("#chatbox").html(html); //Insert chat log into the #chatbox div  
                },  
            });  
        }  

El parámetro url nos indica a qué archivo intentamos acceder por Ajax, cache es para definir si queremos cachear el archivo o no (en este caso, por lógica, no, porque queremos recargarlo en cada llamada) y finalmente la función que se ejecutará en caso de éxito (en este caso insertar el log en el div #chatbowx).

Para añadir autoscrolling, modificas el código anterior añadiendo lo siguiente:

    //Load the file containing the chat log  
    function loadLog(){  
        var oldscrollHeight = $("#chatbox").attr("scrollHeight") - 20; //Scroll height before the request  
        $.ajax({  
            url: "log.html",  
            cache: false,  
            success: function(html){  
                $("#chatbox").html(html); //Insert chat log into the #chatbox div  
                //Auto-scroll  
                var newscrollHeight = $("#chatbox").attr("scrollHeight") - 20; //Scroll height after the request  
                if(newscrollHeight > oldscrollHeight){  
                    $("#chatbox").animate({ scrollTop: newscrollHeight }, 'normal'); //Autoscroll to bottom of div  
                }  
            },  
        });  
    }  

Básicamente consiste en ir modificando el tamaño para ocultar la parte de arriba y ampliar la de abajo. Simple truquillo. Ahora nos falta que el chat se auto recargue, cosa que haremos desde javascript:

setInterval (loadLog, 2500);

Esto lanza la función loadLog cada dos segundos y medio, logrando recargar los datos.

En el artículo orginal además encontraréis al final una serie de artículos con consejos varios para mejorar el código (en inglés) y todo el código completo para descargar.

Redimensionar imágenes en PHP

Es habitual que cuando en una web trabajamos con imágenes tengas que presentar la misma en varios tamaños. Una opción es redimensionarla en pantalla con CSS… pero es una mala opción. Es una mala opción porque lo que haces es cargar una imagen grande y luego procesarla para hacerla pequeña, por lo que ralentizas la carga de la página. ¿La solución? Guardar una copia reducida de la imagen, lo que se llama un thumbnail.

¿Y cómo reducir la imagen? Bueno, PHP incluye la librería GD que nos permite tratar y creawr imágenes. Y además, en este caso, vamos a redimensionar la imagen manteniendo la proporción.

//Ruta de la original
$rtOriginal="/images/example.jpg";
	
//Crear variable de imagen a partir de la original
$original = imagecreatefromjpeg($rtOriginal);
	
//Definir tamaño máximo y mínimo
$max_ancho = 150;
$max_alto = 150;

//Recoger ancho y alto de la original
list($ancho,$alto)=getimagesize($rtOriginal);

//Calcular proporción ancho y alto
$x_ratio = $max_ancho / $ancho;
$y_ratio = $max_alto / $alto;

Con esto ya tenemos los datos para mantener las proporciones de la imagen. Ahora toca calcular el tamaño:

if( ($ancho <= $max_ancho) && ($alto <= $max_alto) ){
//Si es más pequeña que el máximo no redimensionamos
	$ancho_final = $ancho;
	$alto_final = $alto;
}
//si no calculamos si es más alta o más ancha y redimensionamos
elseif (($x_ratio * $alto) < $max_alto){
	$alto_final = ceil($x_ratio * $alto);
	$ancho_final = $max_ancho;
}
else{
	$ancho_final = ceil($y_ratio * $ancho);
	$alto_final = $max_alto;
}

Con esto ya tenemos el tamaño de la imagen con las proporciones guardadas. Finalmente nos queda crear la imagen y guardarla:

//Crear lienzo en blanco con proporciones
$lienzo=imagecreatetruecolor($ancho_final,$alto_final);	

//Copiar $original sobre la imagen que acabamos de crear en blanco ($tmp)
imagecopyresampled($lienzo,$original,0,0,0,0,$ancho_final, $alto_final,$ancho,$alto);

//Limpiar memoria
imagedestroy($original);

//Definimos la calidad de la imagen final
$cal=90;

//Se crea la imagen final en el directorio indicado
imagejpeg($lienzo,"./images/thumb.jpg",$cal);

Todo el cálculo, por otra parte, es necesario sólo si quieres mantener la proporción, si no la cosa queda más sencilla:

//Ruta de la original
$rtOriginal="/images/example.jpg";
	
//Crear variable de imagen a partir de la original
$original = imagecreatefromjpeg($rtOriginal);
	
//Definir tamaño máximo y mínimo
$ancho_final = 150;
$alto_final = 150;

//Recoger ancho y alto de la original
list($ancho,$alto)=getimagesize($rtOriginal);

$lienzo=imagecreatetruecolor($ancho_final,$alto_final);	

//Copiar $original sobre la imagen que acabamos de crear en blanco ($tmp)
imagecopyresampled($lienzo,$original,0,0,0,0,$ancho_final, $alto_final,$ancho,$alto);

//Limpiar memoria
imagedestroy($original);

//Definimos la calidad de la imagen final
$cal=90;

//Se crea la imagen final en el directorio indicado
imagejpeg($lienzo,"./images/thumb.jpg",$cal);

Esto para imágenes jpg. Para png la diferencia sería usar imagepng() para crear, y para gif imagegif(). En la web de php puedes investigar más sobre la librería GD y sacarle todo el jugo.

Seleccionar filas aleatoriamente en MySQL

Existen varias opciones a la hora de seleccionar filas de forma aleatoria en MySQL, haciendo uso de la función RAND(). Vamos a imaginar que quieres obtener 3 filas aleatorias de una base de datos. Lo más sencillo es usar la siguiente sintaxis:

SELECT * FROM tabla ORDER BY RAND() LIMIT 3;

El problema de este método es que tiene que generar una tabla temporal completa con todos los datos de la tabla original reordenados aleatoriamente… en fin, que es una sangría de recursos. Existe una opción, que es generar sólo una tabla con los registros que queramos, mediante una subconsulta. Tal que así:

SELECT * FROM tabla WHERE RAND()<(SELECT ((3/COUNT(*))*10) FROM tabla) ORDER BY RAND() LIMIT 3;

Un método que sigue gastando muchos recursos, pero menos al crear sólo una tabla temporal con la cantidad de filas que necesitamos (cantidad que necesitamos / total * 10).

Pero si tenemos un índice autonúmerico en nuestra tabla podemos ahorrar más recursos:

SELECT t.* FROM tabla AS t JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM tabla)) AS id) AS x WHERE t.id >= x.id LIMIT 3;

Este método no genera un valor aleatorio para cada fila consultada, por lo que ahorra muchos recursos en el servidor. Eso sí, no te dará los registros ordenados aleatoriamente, sino que será registros secuenciales, pero comenzando en un valor aleatorio. Es decir, los dos primeros podrían devolverte las filas 1, 21 y 42, este en cambio te devolvería 1,2,3 – 41,42,43… si quieres que sea un resultado aleatorio de verdad deberías realizar la consulta varias veces con limit 1… con lo que el ahorro de recursos se va al carajo. Además, puede fallar si hay algún hueco en la secuencia del campo autonumérico (por ejemplo, si has borrado un valor).

Una última opción, en lugar de generar el valor aleatorio a nivel de base de datos generarlo antes, en el código del servidor. Un ejemplo en PHP (que también tiraría de id autonumérica y que también daría problemas si tuviera huecos, ojo):

$result = $mysqli->query("SELECT * FROM tabla WHERE id in ROUND(".lcg_value()."*(SELECT COUNT(*) FROM tabla)) LIMIT 1")

El ejemplo de arriba nos devolvería una fila aleatoriamente, por lo que bastaría con repetirla en un bucle tantas veces como necesitemos (en este caso el problema vendría de la cantidad de conexiones que hay que abrir a la base de datos). Puedes intentar implementar lcg_value() en los otros métodos citados, y comprobar si el rendimiento mejora.

Trabajando con JSON en PHP: crear y consumir JSON

JSON se ha convertido en una herramienta fundamental en la programación web, ya sea en servicios web, para comunicar con apps de móviles, pasar datos de php a javascript…

Desde PHP podemos tanto crear un JSON como recibirlo y manejarlo. Para la creación debemos utilizar la función json_encode(), que recibe lo que queramos serializar (una cadena, una variable numérica, un array, un objeto) y devuelve una cadena con los datos serializados. Si le pasamos un array normal o una cadena, no veríamos mucho cambio tras la serialización, pero si le pasamos un array asociativo, tendríamos un resultado como el del ejemplo:

$miArray = array("ferrari"=>"rojo", "porsche"=>"plateado", "mercedes"=>"negro");
$string = json_encode($miArray);
//el valor de string sería {"ferrari":"rojo","porsche":"plateado","mercedes":"negro"} 

Pero la verdadera potencia la encontramos cuando la aplicamos a objetos, ya que JSON nos permite serializar cualquier objeto de PHP, con el mismo procedimiento que usamos arriba: la función json_encode.

Una cuestión a la hora de trabajar con JSON que debemos tener en cuenta es la codificación de caracteres. En principio json_encode() ya convierte a Unicode los resultados, ya que en la notación JSON usamos siempre Unicode. El problema es que PHP por defecto tratará la cadena de origen como UTF-8, así que si no estás trabajando con esta codificación, y estás usando, por ejemplo, ISO-8859-1, se tornará necesario que codifiques las cadenas a UTF-8 antes de convertirlas en JSON. Esto se hace con la función utf8_encode().

$miArray = array("palabro1"=>utf8_enconde("ñandú"), "palabro2"=>"maniqueo", "palabro3"=>utf8_encode("bandoneón");
$cadenajson = json_encode($miArray);

Con esto ya recibiríamos las cadenas codificadas en UTF-8 y prevendríamos errores de codificación.

Ok, con esto hemos visto como generar un JSON, pero ¿cómo lo consumimos?.

Bueno, primero habría que destacar que existen varias librerías para JSON en PHP, pero desde PHP 5.2 este incluye librerías nativas desarrolladas en C, que son las más recomendables por cuestiones de rendimiento. Ahora vamos a suponer un objeto tal como el del ejemplo:

$strjson = '{
   "elemento1": "valor1",
   "elemento2": "valor2",
   "elemento3": null,
   "otroobj": {
      "a ver": "oh",
      "venga": "va"
   };

Esa sería la cadena JSON que recibiría nuestro código PHP con un objeto serializado ¿cómo la transformamos en objeto? Pues si serializábamos con json_encode() el paso contrario será con json_decode():

$objetophp = json_decode($strjson);

//si imprimimos objetophp con print_r() nos dará:
stdClass Object
(
   [elemento1] => valor1
   [elemento2] => valor2
   [elemento3] =>
   [otroobj] => stdClass Object
   (
      [a ver] => oh
      [venga] => va
   )
) 

Al tener un objeto puedes acceder a sus propiedades como en cualquier objeto normal de PHP.

Una serie de avisos: Recuerda, como dijimos antes, que las cadenas deben estar en UTF-8, si usas otra codificación tendrás que tirar de utf8_encode. Los nombres tanto de las propiedades como de los valores deben ir entre comillas dobles, no se aceptan comillas simples y sólo los valores numéricos o null pueden ir sin comillas. No te dejes ni llaves ni corchetes abiertos, ni ninguna coma de más. Todo eso provocará fallos.

Crear un webservice básico con PHP y SOAP

SOAP es un protocolo de intercambio para servicios web basado en XML, sobre el que puedes leer en esta entrada de la wikipedia. Para no liarnos con preámbulos, vamos con la chicha ¿cómo creamos un servicio web en PHP?.

Para facilitarnos la vida empezaremos por descargar NuSOAP, un toolkit para el desarrollo de servicios web con SOAP en PHP, que nos proveerá de diversas clases para trabajar con este protocolo. Basta con descargarlo, descomprimirlo, meterlo dentro de nuestro proyecto y, cuando queramos usarlo, incluir nusoap.php como librería.

Ok, con NuSOAP instalado toca crear un servidor SOAP en nuestra aplicación. Para el ejemplo crearemos un servidor, lo llamaremos producto.php, que si recibe una petición donde se le pida una lista de libros devuelva tres títulos (es un ejemplo básico, piensa que en la realiad podrías acceder a una base de datos y dar muchas más funcionalidades).

<?php
	require_once "nusoap.php";
	 
        function getProd($categoria) {
	    if ($categoria == "libros") {
	        return join(",", array(
	            "El señor de los anillos",
	            "Los límites de la Fundación",
	            "The Rails Way"));
	    }
	    else {
	            return "No hay productos de esta categoria";
	    }
	}
	 
	$server = new soap_server();
	$server->register("getProd");
	$server->service($HTTP_RAW_POST_DATA);
?>

Ok, ahora necesitas un cliente, que llamaremos cliente.php. En el constructor, el cliente recibirá el url del servidor y para acceder al método que nos devuelve los libros recurriremos al método call(), al cual le pasaremos el nombre del método del servidor al que queremos acceder y los parámetros en forma de array. Además, también controlaremos que no haya errores en la comunicación.

<?php
	require_once "nusoap.php";
	$cliente = new nusoap_client("http://localhost/producto.php");
	 
	$error = $cliente->getError();
	if ($error) {
	    echo "<h2>Constructor error</h2><pre>" . $error . "</pre>";
	}
	 
	$result = $cliente->call("getProd", array("categoria" => "libros"));
	 
	if ($cliente->fault) {
	    echo "<h2>Fault</h2><pre>";
	    print_r($result);
	    echo "</pre>";
	}
	else {
	    $error = $cliente->getError();
	    if ($error) {
	        echo "<h2>Error</h2><pre>" . $error . "</pre>";
	    }
	    else {
	        echo "<h2>Libros</h2><pre>";
	        echo $result;
	        echo "</pre>";
	    }
	}
?>

Con esto ya tienes una idea múy básica del funcionamiento de un webservice SOAP construído con PHP. Pero claro, nos falta un archivo WSDL para tener un webservice decente. Aunque dicho archivo puede ser escrito a mano, NuSOAP puede generarlo por ti pasándole ciertos parámetros, por lo que lo ideal sería generarlo en el servidor. Así que modifica tu producto.php para que quede tal que así:

<?php
	require_once "nusoap.php";
	 
	function getProd($categoria) {
	    if ($categoria == "libros") {
	        return join(",", array(
	            "El señor de los anillos",
	            "Los límites de la Fundación",
	            "The Rails Way"));
	    }
	    else {
	        return "No hay productos de esta categoria";
	    }
	}
	 
	$server = new soap_server();
	$server->configureWSDL("producto", "urn:producto");
	 
	$server->register("getProd",
	    array("categoria" => "xsd:string"),
	    array("return" => "xsd:string"),
	    "urn:producto",
	    "urn:producto#getProd",
	    "rpc",
	    "encoded",
	    "Nos da una lista de productos de cada categoría");
	 
	$server->service($HTTP_RAW_POST_DATA);
?>

Como ves, el cambio en es cuando llamamos a register, ya que en vez de pasarle, como antes, el método en cuestión, le añadimos también varios argumentos para generar el WSDL:

  • El primer array nos permite definir el argumento de entrada y su tipo de datos
  • El segundo define la función de retorno y su tipo de datos
  • urn:producto es la definición del namespace
  • urn:producto#getProd es donde definimos la acción SOAP
  • Luego viene el tipo de llamada,que puede ser rpc, como en el ejemplo, o document
  • Tras esto definimos el valor del atribute use, que puede ser encoded o literal
  • Finalmente viene una descripción de qué hace el método al que llamamos

Ahora basta con que en el navegador accedas a producto.php?wsdl y verás el WSDL generado. Ya puedes copiarlo y añadirlo a tu directorio web (crea un archivo y llámalo, por ejemplo, libros.wsdl). Para que el cliente lo utilice debes modificar el código, y en el constructor, en vez del url le pasas el nombre del archivo, de forma que quede como en el ejemplo:

         $client = new nusoap_client("libros.wsdl", true);

Ahora sí, ya tienes montado un pequeño servicio web. El ejemplo es simplón, pero piensa en todas las funcionalidades que podrías incorporarle.

Métodos mágicos en PHP

Reconozco que no había oído hablar de esta característica de PHP 5 hasta estas navidades, cuando un experimentado programador php me comentó que era algo que debía aprender (el mismo que me recomendó aprender Symphony, por cierto). La verdad es que el nombre suena rimbobante: «Métodos mágicos»… te quedas como «no se si será una decepción o si será la leche». Luego, curiosamente durante el transcurso de una entrevista laboral me volvieron a preguntar si sabía del tema, así que decidí informarme.

Tras leer sobre el tema he de decir que no me queda muy claro del todo el uso de dichos métodos. Se trata de una serie de métodos comunes a todas las clases que se crearon para PHP5 y que refuerzan la orientación a objetos del lenguaje. Estos métodos comienzan su nombre con __ por lo que se recomienda no crear métodos propios que empiecen así. Los citados métodos mágicos son:

  • __construct()
  • __destruct()
  • __call()
  • __callStatic()
  • __get()
  • __set()
  • __isset()
  • __unset()
  • __sleep()
  • __wakeup()
  • __toString()
  • __invoke()
  • __setState()
  • __clone()

Si has programado en Java unos cuántos ya te sonarán, porque no es sólo que se llamen igual, es que tienen una funcionalidad similar.

Por lo que he entendido leyendo la documentación, el método __sleep() es llamado cuando se utiliza el método serialize(), se ejecuta antes de comenzar la serialiación y nos permite definir un array con los nombres de todas las variables del objeto que se va a serializar. Por su parte __wakeup() nos permitiría reinicilizar el objeto después de la serialización (restablecer conexiones a la base de datos, por ejemplo), y se ejecuta cuando se utliza unserialize().

Por su parte __invoke() es llamado cuando se intenta llamar a un objeto como si fuera una función. Sí, suena raro, así que os pongo un ejemplo:

<?php
class CallableClass
{
    public function __invoke($x)
    {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5);
?>

//esto devolvería int(5)

En cuanto a __toString(), nos permite decidir qué se imprimirá si el objeto es llamado como si fuera una cadena de texto. Por ejemplo, si haces echo $objeto.


<?php
class TesteandoToString
{
    public $imprimir;

    public function __construct($imprimir)
    {
        $this->imprimir = $imprimir;
    }

    public function __toString()
    {
        return $this->imprimir;
    }
}

$class = new TesteandoToString('Hola caracola');
echo $class;

//esto imprimiría Hola caracola
?>

El método __set_state() nos permite recoger un array con las variables exportadas por el método var_export() y asignarlas a un nuevo objeto. No confundir con la funcionalidad del método __clone(), que directamente crea una copia exacta de otro objeto.

Los métodos __call(), __callStatic(), __set(), __get(), __isset() y __unset() se utilizan para la sobrecarga, lo que nos permite «crear» dinámicamente propiedades y métodos. A estos métodos no se les pueden pasar parámetros por referencia. Se invoca a estos métodos cuando queremos trabajar con propiedades o métodos que no se han declarado o que no son visibles. El funcionamiento de __get() y __set() es similar al que pueden tener en Java los métodos get() y set(): el primero nos permite acceder a las propiedades privadas del objeto desde fuera de la clase y el segundo nos permite modificarlos. Puedes imaginar que __unset() lo que hará será «limpiar» de valor la propiedad y que __isset() funciona como isset() pero permitiendo el acceso a los valores privados. En cuanto a __call(), nos permitirá llamar a métodos inaccesibles en un contexto de objeto, y __callStatic() lo mismo pero en un contexto estático.

Finalmente __construct() es un método constructor, y será lo primero que se ejecute al crear un nuevo objeto de la clase, y __destruct() será lo último que se ejecute antes de eliminar el objeto de la memoria.

En fin, con todos estos métodos está claro que PHP gana mucho en cuanto a orientación a objetos.

Evitar SQL-injection en PHP

Los ataques SQL-Injection son unos de los más habituales en el mundo web. Aquí vamos a ver una serie de consejos para evitar estos agujeros en la seguridad:

Lo primero es seguir una serie de consejos a nivel de administración, como limitar los permisos del usuario a nivel de base de datos. Por ejemplo, en la mayoría de aplicaciones web lo habitual es que no tenga que utilizarse DROP, por lo que por seguridad sería mejor no permitir ya al usuario hacerlo. También sería interesante no dar información extra al atacante evitando sacar en pantalla los errores de la base de datos. En ese caso lo ideal es capturar el error haciendo uso de exception. Tampoco hay que confiarlo todo a las validaciones por javascript, porque el atacante puede saltárselas desactivándolo desde el navegador, por lo que mejor hacer las validaciones del lado del servidor. Y finalmente escapar las comillas con msqli_real_scape_string (en caso de que usemos MySQL), pg_scape_string (en caso de PostgreSQL) o addslashes (por si utilizamos otro SGBD). También podemos utilizar htmlentities para convertir los textos en entidades html, como un plus a la seguridad. Abajo un simple ejemplo:


<?php

try{
  $query = sprintf("SELECT * FROM users WHERE id=%d", mysqli_real_escape_string($id));
  $query = htmlentities($query);
  mysqli_query($query);
}catch(Exception $e){
    echo('Lo sentimos, ha habido un error en la conexión');
}
?>

Como medida extra se podrían utilizar expresiones regulares para evitar la inserción de ciertas palabras (SELECT, DROP, UNION…), si bien puede resultar poco práctico, sobre todo si tu software está destinado al mercado británico.

Pero si todo esto te parece lioso, hay una alternativa: la clase PDO. Dicha clase nos facilitará mucho la vida a la hora de trabajar con bases de datos, ya que nos permite abstraernos del SGDB que estemos utilizando. Si por ejemplo, en una página donde no utilices PDO o algo similar sino el mysql_connect simple, decides migrar tu web de MySQL a PostgreSQL tendrías que cambiar todos los métodos del conector de MySQL por los métodos de PostgreSQL. Con PDO bastaría con que cambiaras una sola línea de código, concretamente la de la creación del objeto PDO, y el resto de la aplicación seguiría funcionando. PDO además te permitirá usar consultas parametrizadas (como los Prepared Statements de java) o realizar transacciones. En fin, en el enlace de arriba tenéis todo el manual de PDO para estudiarlo si queréis. Ahora vamos con un simple ejemplo de Prepared Statement, para que veáis lo sencillo que es (dando por sentado para el ejemplo que ya hemos creado el objeto PDO, tal cual está explicado en el manual del enlace).


<?php
  $prepared_statement = $pdo->prepare("SELECT name FROM usuarios WHERE id = :id");/*preparamos la consulta*/
  $prepared_statement->bindParam(':id', $_GET['id'], PDO::PARAM_INT); /*Le pasamos el parámetro, asociado al parámetro de la consulta y definiendo su tipo (si no, por defecto lo trata como string)*/
  $prepared_statement->execute(); /*ejecutamos*/
  $prepared_statement = $statement->fetch(); /*recogemos los resultados, como un array. Se pueden utilizar parámetros para especificar otro tipo de respuesta, como por ejemplo PDO::FETCH_ASSOC para obtener un array asociativo*/
?>

Y con estos breves consejos lograrás que tu página sea más segura. El consejo: utiliza PDO, por ahorrarte comeduras de cabeza, por seguridad y por portabilidad de tu código.