PHP Manual: Debug en PHP

Debug en PHP


Sumario:


Consideraciones

Escribir código es sólo una parte de la programación, y quizá no la más importante. Por otra está el diseño del algoritmo, y las tareas de debug que verifican el correcto funcionamiento del programa en todos los casos.
En esta parte se centrará este manual. Al margen quedan los errores generados por PHP. Un parse error, un warning o un error fatal tienen en la mayoría de los casos una resolución inmediata, y generalmente tienen que ver con la sintaxis.
Siempre que en este documento se hable de errores, no nos referiremos a errores sintácticos sino a errores funcionales, aquéllos que a pesar de ser silenciosos hacen que nuestro script no se comporte como nosotros esperábamos.
Para asegurarnos de que éste es realmente nuestro caso, y antes de comenzar a debugear, no puede haber ningún error de PHP (parse errors, warnings o fatal errors...(los notices los dejo a gusto del consumidor)).

Para ello comprobad en vuestro php.ini o haciendo un <?php phpinfo(); ?> que la directiva display_errors se encuentra a On y que error_reporting se encuentra como mínimo a E_ALL & ~E_NOTICE (2039) ó E_ALL (2048).

Esto suele ser así en casi todos los servidores, aunque algunas configuraciones por defecto desactivan estas directivas. Esto está bien para servidores de producción, en los que un mensaje de error revela paths e información que puede comprometer la seguridad; sin embargo en el servidor de un programador, es casi un imperativo activarlas si no queremos programar a ciegas. Cuando nos libremos de los mensajes de PHP, y nuestro script no se comporte de forma adecuada, es cuando deberemos comenzar nuestras tareas de debug.

Principios del debug

Debugear es la reacción natural de un buen programador ante un problema en su script. Es sorprendente ver que los usuarios que llegan al canal con dudas, a veces desesperados por no poder solucionar un error funcional no han realizado el más mínimo debug en el código para intentar arreglarlo. Por tanto, creo que el primer principio del debug debería ser:

1. El código no se arregla sólo

Puedes patalear, llorar y entrar en el canal a dar gritos, pero ésta es una verdad como un templo. Simplemente cruzándote de brazos y ejecutando el script una y otra vez no se arreglará el problema por arte de magia. ¿Parece lógico verdad? Pues no debe parecerlo tanto cuando la mayoría de los usuarios no realizan tareas de debug.

¿Por qué el usuario no debugea? Bueno, hay dos respuestas, pero se resumen en una: tozudez. Siempre intentamos atribuir a otros nuestros propios errores. En la vida real puede colar, pero en programación no. PHP nunca tiene la culpa de que el script no funcione, de hecho tu script hace exactamente lo que le pides. El problema radica en que lo que le pides no coincide con lo que tú crees que le pides. Así pues, el segundo principio del debug es:

2. El script no funciona porque el programador ha cometido uno o más errores.

(con una pobabilidad infinitesimal de que nos encontremos con un bug REAL en PHP)

La segunda respuesta a la pregunta anterior genera otra afirmación básica. El programador, en su tozudez repasa su script, lee las instrucciones, mira las variables. En nuestro repaso mental del código presuponemos que todas las variables contienen lo que esperamos, que todos los bucles iteran como deberían, que todas las condiciones se cumplen.
Este es el error más común. No te creas nada, compruébalo tú mismo y...

3. No des NADA por supuesto

El debug es una oportunidad de aprender. Debugeando conocerás como funciona tu script hasta las últimas consecuencias. Aprenderás a enfrentarte a los problemas tú mismo.
Algunos usuarios prefieren, en último caso, borrar todo el script y empezarlo desde 0. Esto es siempre un error, perderás la oportunidad de aprender y estarás condenado a repetir una y otra vez tus mismos fallos.
Otros usuarios prefieren la técnica del ensayo-error, toquetear en el script hasta que funcione. Esto puede estar bien para los monos y otro tipo de simios. Pero tú no lo eres. Piensa bien todos los cambios que realices al script y por qué pones o dejas de poner cada cosa. En definitiva:

4. Usa la cabeza

El debug es el arte de eliminar probabilidades de error. Cuando nos encontramos con un script que falla, y lo miramos desde el punto de vista del principio 3, las posibles cosas que pueden producir el fallo se multiplican. Debugear es el arte de ver qué se cumple y qué se deja de cumplir en tu script, acotando el error, reduciendo las posibilidades hasta que sabemos con certeza qué es lo que falla. En cuanto sabemos lo que falla, su resolución suele ser trivial. Por eso, el 5º principio del debug es:

5. El principal error es no saber qué produce el error

El último principio es también de pura lógica. Lo pongo porque todos sabemos que si bien la lógica no es una especie en extinción, al menos escasea bastante. Algunas mentes cuadriculadas cuando se les manda debugear con un echo o similar en el canal, vuelven al mismo y responden "sigue sin ir". Si has comprendido el principio 5 habrás deducido que...

6. El debug no corrige por sí mismo los errores

Tan sólo localiza el error.

Herramientas básicas del debug

En PHP existen dos herramientas que casi todos los que se lanzan a programar en este lenguaje conocen. No se trata de funciones sofisticadas, pero servirán a la perfección para nuestro propósito.

echo

Esta construcción del lenguaje nos valdrá para tres cosas:

- Volcar el contenido de una variable para verificar que tiene asignado el valor deseado:
Para ello es recomendable añadir un texto previo, ya que si la variable está vacía podríamos no darnos cuenta:
Ejemplo:

<?php
echo 'el valor de $variable_a_debugear es '.$variable_a_debugear;
?>


- Situarnos dentro de la ejecución del script:
Con un echo podremos comprobar si nuestro script ha interpretado una serie de instrucciones que se encuentren dentro de una condición if.
Ejemplo:

<?php
if (!empty($_GET['variable'])) {
    echo
'Se cumple if. la variable no está vacía<br />';
    
// instrucciones
}


- Comprobar las veces que itera un bucle:
Ejemplo:

<?php
for ($i=0;!feof($puntero);$i++) {
    echo
'iteracion numero '.$i.'<br />';
    
// resto de instrucciones
}


Mucho más sobre el echo en FAQ del echo y strings

print_r()

Con print_r podremos fácilmente comprobar tanto la estructura como el contenido de cualquier array.
Ejemplo:

<?php
print_r
($_SESSION);
?>


Mostraría las variables de sesión que estén disponibles en el script. Por ejemplo:
Array
(
    [usuario] => pepe
    [tiempo] => 1083876837
)
Se ve de forma intuitiva la estructura del array, si posee mas de una dimensión, o si se trata de un array asociativo:

<?php
$array
=array("verduras"=>array("alcachofa","puerro","berza"),
             
"frutas"=>array("invierno"=>naranja,"verano"=>array("fresa","melocoton","higo")));
print_r($array);
?>
                


Array
(
    [verduras] => Array
        (
            [0] => alcachofa
            [1] => puerro
            [2] => berza
        )

    [frutas] => Array
        (
            [invierno] => naranja
            [verano] => Array
                (
                    [0] => fresa
                    [1] => melocoton
                    [2] => higo
                )

        )

)
Si estamos visualizando un print_r en el navegador, sería deseable escribirlo de esta forma:

<?php
echo '<pre>'; print_r($array); echo '</pre>';
?>


En caso contrario nos aparecerá en una única línea y no podremos observar bien la estructura del array.

Algunos ejemplos prácticos

Los ejemplos que se muestran pueden parecer triviales. Donde realmente se comprueba lo efectivo del debug, es en scripts más complejos, con múltiples archivos, includes... o sea, en los casos reales. Así todo estas pequeñas muestras puede que ayuden a entender los conceptos del debug.
Imaginad que tenemos este sencillo script:


<?php
if (isset($_POST['enviar'])) {
    if (isset(
$_POST['usuario'])) {
    
        
header("Location: http://midominio.com/index.php");
    }
}
?>    

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
  <input type="text" name="Usuario">
  <select name="pais">
    <option value="es">España</option>
    <option value="fr">Francia</option>
  </select>
  <input type="submit" name="enviar" value="Enviar">
</form>


Ya os adelanto que este script contiene errores. No son muy difíciles de ver, y un programador medianamente experimentado no necesitaría debug. De lo que se trata es de iniciar un proceso metódico que nos diga exactamente cuál es el fallo si no aciertas verlo.

Alguien que tuviera este script entraría al canal y diría "no funciona", o "no me redirige bien". Esta pregunta es en cualquier caso errónea: el usuario tiene en cuenta el efecto, pero no la causa.
Lo primero es verificar si el programa entra en el if, para descartar que se trate de un error al hacer el header.
Como se dice en el principio 4 -> "No des NADA por supuesto". Siempre tendemos a tener prejuicios y dar por hecho que los errores se acumulan en las funciones que menos conocemos o que suelen dar más problemas como header(), y no siempre es así. Procedamos pues a descartar:


<?php
if (isset($_POST['enviar'])) {
    if (isset(
$_POST['usuario'])) {
    
        echo
"Se ha enviado un nombre de usuario";
        
        
// Comentamos temporalmente las funciones que puedan alterar el flujo del programa
        // como por ejemplo header()
        //header("Location: http://midominio.com/index.php");
    
}
}
?>    

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
  <input type="text" name="Usuario">
  <select name="pais">
    <option value="es">España</option>
    <option value="fr">Francia</option>
  </select>
  <input type="submit" name="enviar" value="Enviar">
</form>


Acabamos de descubrir que nuestro error, de momento no tiene nada que ver con header ya que el echo no aparece al ejecutar el script. Ya estamos acotando el error. Sabemos que ese if no se cumple, ahora bien. ¿Por qué? ¿No llega por POST?. Comprobémoslo.


<?php

// ¿Qué está llegando por POST a nuestro script?

echo '<pre>'; print_r($_POST); echo '</pre>';

if (isset(
$_POST['enviar'])) {
    if (isset(
$_POST['usuario'])) {
    
        echo
"Se ha enviado un nombre de usuario";
        
        
// Comentamos temporalmente las funciones que puedan alterar el flujo del programa
        // como por ejemplo header()
        //header("Location: http://midominio.com/index.php");
    
}
}
?>    

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
  <input type="text" name="Usuario">
  <select name="pais">
    <option value="es">España</option>
    <option value="fr">Francia</option>
  </select>
  <input type="submit" name="enviar" value="Enviar">
</form>


El print_r resultante podría ser el siguiente:
Array
(
    [Usuario] => pepe
    [pais] => es
    [enviar] => Enviar
)
Si aún no vemos el error, podemos hacer un echo $_POST['usuario']; Si no lo sabíamos de antemano, descubriremos que en PHP $Usuario y $usuario son dos variables distintas, y que hemos de tener mas cuidado a la hora de escribirlas.
Tras corregir "usuario" por "Usuario" veremos como en efecto, el script entra en el if como esperamos.
En caso de no recibir nada del print_r($_POST); aparte de un array vacío, el problema sería otro, como por ejemplo que nos hemos olvidado de ponerle method="post" al formulario.


Imaginad ahora que tenemos el siguiente archivo de texto: usuarios.txt con usuarios y sus passwords


pepe:Abx4Dt6qfO
o'connel:r4fB8jS3aP
jeremias:m3cciElB23
thessoro:zOd3aM3rR7


En el siguiente script verificamos que el user/password introducido es correcto


<?php

// Supongamos que, de un formulario que tiene como destino este script
// nos llegan los siguientes datos:
// $_POST['usuario']=jeremias
// $_POST['password']=m3cciElB23

$array = file("usuarios.txt");
foreach (
$array as $linea) {
    list(
$user,$pass) = explode(":",$linea);
    if (
$_POST['usuario']==$user && $_POST['password']==$pass) {
        
header("Location: http://mipagina.com/usuario_ok.php");
        exit();
    }
}
echo
"Usuario/password incorrecto";
?>


Este script tendría un comportamiento muy raro. Después de testearlo descubriríamos que sólo funciona si nos logueamos con el user/password de thessoro. ¿Extraño verdad?

<?php

// Supongamos que, de un formulario que tiene como destino este script
// nos llegan los siguientes datos:
// $_POST['usuario']=jeremias
// $_POST['password']=m3cciElB23

$array = file("usuarios.txt");

// ¿que diablos pasa con este array?
echo '<pre>'; print_r($array); echo '</pre>';

foreach (
$array as $linea) {
    list(
$user,$pass) = explode(":",$linea);
    if (
$_POST['usuario']==$user && $_POST['password']==$pass) {
        
// header("Location: http://mipagina.com/usuario_ok.php");
        
exit();
    }
}
echo
"Usuario/password incorrecto";
?>    


nos daría el siguiente resultado:
Array
(
    [0] => pepe:Abx4Dt6qfO

    [1] => o'connel:r4fB8jS3aP

    [2] => jeremias:m3cciElB23

    [3] => thessoro:zOd3aM3rR7
)
En este momento, unos ojos avispados acostumbrados a ver un montón de print_r's dará con el problema.
¿Que diferencia hay entre este print_r y los anteriores?
Uhmmm en efecto, hay saltos de línea al final de cada elemento, excepto en el de thessoro, que es el último del archivo. Por eso, la password no se verifica: "m3cciElB23\n" no es igual a "m3cciElB23".
Si no lo sabíamos de antes, lo habremos aprendido con el debug. file() mete a un array las lineas del fichero ¡incluyendo los saltos de línea!. La solución sería pues:

<?php

// Supongamos que, de un formulario que tiene como destino este script
// nos llegan los siguientes datos:
// $_POST['usuario']=jeremias
// $_POST['password']=m3cciElB23

$array = file("usuarios.txt");
foreach (
$array as $linea) {

    
// quitamos los saltos de linea, y espacios de cada linea
    
$linea=trim($linea);
    
    list(
$user,$pass) = explode(":",$linea);
    if (
$_POST['usuario']==$user && $_POST['password']==$pass) {
        
header("Location: http://mipagina.com/usuario_ok.php");
        exit();
    }
}
echo
"Usuario/password incorrecto";
?>        


La solución es un trim. tarán!!

Debugeando un query MySQL

Debugear queries MySQL se escapa un poco de la dinámica anterior, pero he decidido incluir esta sección por dos motivos:

- Los errores en queries MySQL también pueden ser silenciosos y necesitan debug.
- Warning: supplied argument is not a valid MySQL result resource. A pesar de ser un warning, se debugea de la misma forma.

Cuando mysql_num_rows, mysql_fetch_array o cualquier variante (mysql_fetch_*) nos da un error como el anterior puede ser por 3 motivos:



EOF



Debug en PHP thessoro © 2004 Todos los derechos reservados.


PHP-Hispano.net - Porque al final, todos acabamos aprendiendo.