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.
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...
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 }
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'])) {
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"); } } ?>
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"); } } ?>
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.
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:
No se ha podido conectar con la base de datos y nuestro mysql_connect tiene la salida anulada: @mysql_connect
En caso contrario, nos habría aparecido un error de conexión previo al warning. Recordemos
que los errores de PHP que nos aparezcan han de corregirse
en riguroso orden de aparición ya que en ocasiones errores posteriores dependen de los primeros, y corregidos estos
el resto desaparecen.
Para evitar esto bastará cambiar nuestro mysql_connect:
<?php
@mysql_connect($host,$user,$pass) or die (mysql_error());
// Nota: // mysql_connect($host,$user,$pass); también nos valdría. En este caso // la ejecución del script continuaría, y aparecerían un montón de warnings // por cada query posterior. En el ejemplo el script detendría su ejecución // en caso de que no se pudiera conectar a la base de datos. ?>
Nos hemos equivocado al seleccionar la base de datos.
Este error es silencioso a no ser que imprimamos explícitamente el mysql_error. En caso contrario, nos aparecerá directamente
el Warning: supplied argument is not a valid MySQL result resource
Para evitar esto pondremos:
<?php
@mysql_connect($host,$user,$pass) or die (mysql_error()); mysql_select_db("nombre_db") or die (mysql_error());
// Si nos equivocamos en el nombre de la db, mysql_error() nos dirá: // Unknown database: nombre_db // Y se detendrá la ejecución del script. En caso contrario, no nos // avisará del error, y lo que fallarán parecerán ser los queries. ?>
El query es erróneo
Una vez hemos llegado hasta aquí, sabemos que si obtenemos Warning: supplied argument is not a valid MySQL result resource es
debido a un query erróneo.
Si no somos capaces de ver por qué falla, usaremos este sencillo
procedimiento que nos desenmascara el 100% de los queries erróneos:
tanto los que provocan un warning como los que no hacen lo que
esperamos.
<?php
// Haremos un echo del query, y visualizaremos un posible error con mysql_error()
$link=@mysql_connect($host,$user,$pass) or die (mysql_error());
mysql_select_db("nombre_db") or die (mysql_error());
$query="SELECT campo1,campo2,campo3 FROM tabla WHERE campo1 LIKE '%{$_POST['nombre']}%' OR campo2 = '".$_SESSION['pais']."' ORDER BY campo3 DESC LIMIT 3";
echo 'Query ->'.$query.'<br />';
$result_id=mysql_query($query,$link) or die (mysql_error());
while ($datos=mysql_fetch_assoc($result_id)) { echo '<pre>'; print_r($datos); echo '</pre>'; } ?>
- El echo nos revelará los errores funcionales:
Si alguna variable llega vacía:
Error funcional: Si tenemos "SELECT * FROM tabla WHERE campo='$nombre'" y $nombre no tiene valor, el query sería correcto
de todas maneras y MySQL nos devolvería todos los registros donde campo estuviera vacío.
Errores relacionados con slashes:
MySQL tampoco nos daría error, pero lo descubriríamos al hacer echo.
Si tenemos: "INSERT INTO tabla (campo) VALUES('$nombre')" y $nombre vale "o'connel", veremos el siguiente query:
"INSERT INTO tabla (campo) VALUES('o'connel')" : No da error, pero no se inserta el registro, la solución sería un addslashes.
"INSERT INTO tabla (campo) VALUES('o\'connel')". Este query se comportaría como esperamos.
- El or die(mysql_error()); nos revelará errores sintácticos:
Nombre incorrecto de campos
Nombre incorrecto de tablas
Orden incorrecto en cláusulas
cláusula LIMIT antes de WHERE, ORDER BY después de LIMIT... etc
...
Una última nota: mysql_num_rows y todo el repertorio de funciones
mysql_fetch_* no dan error si el query no devuelve resultados. Un query
que no devuelve resultados es perfectamente correcto.
Sin embargo mysql_result espera un resultado, si no lo hay, dará un
warning. Para solucionarlo, sólo deberíamos hacer un mysql_result en
caso de que un mysql_num_rows anterior diera > 1.