Bucles en PHP

Finalidad de este Documento

Tras bastante tiempo de estancia en el canal #php_para_torpes del iRC-Hispano (www.php-hispano.net) y tras haber ojeado código de mucha gente, una cosa de las que me molesta es el mal uso de los bucles (y del explode). Con este documento trato de explicar el funcionamiento de los diferentes bucles, ademas de e que situación usar uno o otro.

Autor

Este documento ha sido realizado por Iván Arias para #php_para_torpes del iRC-Hispano y www.php-hispano.net. Si encontraste este documento en otro lado seguramente sean en la página de un lamer.

¿Qué es un bucle?

Los bucles son estructuras de control del flujo del programa que nos premiten ejecutar un fragmento de código varias veces. Por ejemplo para aplicar una función a cada elemento de un array (si descontamos el uso de las funciones de tratamiento de arrays), o leer de un Resource mientras queden elementos. Bucle While

El bucle while es quizas el más usado por la gente, aunque la mayoría de las veces veces su uso no es el adecuado. La estructura del bucle es esta:

while (condicion)
  Bloque de codigo

El bloque de codigo se ejecutará mientras la condición se cumpla y puede ser una instrucción, o una serie de instrucciones entre llaves. La condición es una expresión booleana, si se evalua true el bucle continuará su ejecucion hasta que la expresión cambie y de false. De no cambiar nunca el bucle se ejecutaría infinitamente.

PHP, a la hora de evaluar una condición, si esta se trata de una variable, evaluará siempre a true salvo que tenga el valor null, 0, false o la variable sea un array vacio.

El bucle while evalua la condición antes de entrar, si la condición fuera false en un principio, el código no llegaría a ejecutarse. Algún ejemplo de uso del bucle while seria: Eliminar los espacios al principio de una cadena:

<?php
$cadena = "   hola";
while ($cadena{0} == " ") // Se ejecutará mientras el primer caracter de la cadena sea un espacio
  $cadena = substr($cadena, 1); // Eliminamos el primer caracter de la cadena
?>

Meter en un array los archivos de un directorio:

<?php
$dir = opendir("directorio"); // Abrimos el directorio
$archivos = array();
while ($file = readdir($dir)) // Mientras se lean archivos...
  if (is_file("directorio/".$file)) // ...y sean archivos...
    $archivos[] = $file; // ...los metemos en el array
?>

Puede ser algo confusa la condición del úlimo ejemplo. Cuando evaluas una asignación, la variable toma el valor requerido y se evalua esta variable como vimos anteriormente. Es decir, $file tomaria el valor retornado por readdir y php evaluaría el valor de esta variable.

Esta manera de leer del directorio no es la mas correcta, como vimos anteriormente PHP evalua a false varias cosas, si tuvieramos por ejemplo un directorio llamado "0" el bucle se pararía. La solución a este problema es facil, sólo queremos que pare el bucle cuando $file tome el valor false, por lo que tenemos que comparar el valor obtenido con false. La manera correcta de hacer esto sería:

<?php
$dir = opendir("directorio"); // Abrimos el directorio
$archivos = array();
while (($file = readdir($dir)) !== false) // Mientras se lean archivos...
  if (is_file("directorio/".$file)) // ...y sean archivos...
    $archivos[] = $file; // ...los metemos en el array
?>

Bucle do/while

Este bucle sólo se diferencia del while en una pequeña cosa, el while evalua la condición al principio y puede llegar a no ejecutarse; el do/while evalua la condición despues de ejecutar el código, con lo cual el código al menos se ejecutará una vez. La estructura del bucle es la siguente:

do
  Bloque de código
while (Condicion);

Al igual que en el while el Bloque de código puede ser una instrucción o varias metidas entre llaves, y se ejecutará mientras la condición sea cierta. Un ejemplo de uso puede ser:

Leer ordenes de la consola

<?php
$consola = fopen("php://stdin", "r"); // Abrimos para leer de la consola de php
do { // Inicio del bucle
  echo "Introcude una accion:\n";
  $read = trim(fread($consola, 512));
} while ($read != "salir"); // Repetimos mientras no se lea la orden de salir
echo "Saliendo...";
fclose($consola);
?>

Un ejemplo de uso incorrecto de un bucle while sería:

<?php
$consola = fopen("php://stdin", "r"); // Abrimos para leer de la consola de php
$read = "";
while ($read != "salir") {
  echo "Introcude una accion:\n";
  $read = trim(fread($consola, 512));
}
echo "Saliendo...";
fclose($consola);
?>
<?php
$consola = fopen("php://stdin", "r"); // Abrimos para leer de la consola de php
echo "Introcude una accion:\n";
$read = trim(fread($consola, 512));
while ($read != "salir") {
  echo "Introcude una accion:\n";
  $read = trim(fread($consola, 512));
}
echo "Saliendo...";
fclose($consola);
?>

Siempre que tengamos un bucle while, y tengamos que dar un valor a una variable de tal manera que la condición del while evalue a true, o poner el codigo del bucle fuera para que se ejecute una vez, lo más seguro es que necesitemos usar do/while en vez de while.

Bucle for

Este es mi bucle preferido. La mayoría de la gente piensa que este bucle sólo sirve para hacer algo un determinado número de veces, pero se equivoca. El comportamiento de este bucle es identico al del bucle while, la diferencia reside en que el bucle for además se puede usar un contador. La estructura del bucle es la siguiente:

for (Expresión inicial; Condición; Expresión incremental)
  Bloque de codigo

Antes de ejecutar el bucle, se ejecuta la expresión inicial, en la cual podemos dar valor inicial a una variable, resetear un array, abrir un recurso... A continuación se evalua la condición de la misma manera que en los demás bucles, de ser cierta se ejecuta el bloque de código, y por último se ejecuta la expresión incremental.

Así teóricamente suena muy complicado, pero en la práctica es facil de usar. Veamos unos ejemplos aclaratorios:

Crear un array con 10 numeros aleatorios

<?php
for ($i=0, $numeros = array(); $i<10; $i++) { // El bucle se ejecutaria 10 veces
  $numeros[$i] = rand(); // $i tomará valores desde 0 hasta 9, ambos inclusive
}
?>

Mostrar un array por pantalla

<?php
for ($i=0; $i<count($array); $i++) // El bucle se ejecuta tantas veces como posiciones tenga el array
  echo $array[$i]."\n"; // Con el contador accedemos a la posición del array correspondiente
?>

Otro metodo de mostrar un array, si el array tuviese algun valor que PHP evaluase a false el bucle pararía

<?php
for (reset($array); $valor=current($array); next($array)) // Reiniciamos el array, almacenamos el valor actual en $valor y avanzamos a la siguiente posición
  echo $valor."\n";
?>

Asi arreglamos el problema anterior

<?php
for (reset($array); list(,$valor)=each($array); ) // No necesitamos expresion incremental dado que each ya avanza en el array
  echo $valor."\n";
?>

Leer el contenido de un archivo a un string

<?php
$string = "";
for ($archivo=fopen("archivo.txt", "r"); !feof($archivo); $string .= fread($archivo, 512));
fclose($archivo);
?>

Como podemos observar, el bucle for es el más versatil de todos. Cuando tengamos necesidad de usa un bucle while con un contador, lo mas seguro es que necesitemos usar un bucle for.

Bucle foreach

El bucle foreach es un bucle propio de php utilizado para recorrer arrays de una manera más facil. La construcción de este bucle admite dos variantes:

foreach(array as valor)
  Bloque de código

foreach(array as clave => valor)
  Bloque de código

Este bucle ya es bastante diferente a los demás, tanto array, como clave y valor son variables. En cada iteración del bucle, clave y valor cambiarán de valor, clave contendrá la clave de la posición actual del array y valor contendrá su valor.

Veamos algun ejemplo practico:

Mostrar un array por pantalla, podemos observar que el código es más sencillo que si usasemos un bucle for

<?php
foreach($array as $valor)
  echo $valor."<br>";
?>

Si ademas qusiesemos mostrar la posición en la que se encuentra

<?php
foreach($array as $clave => $valor)
  echo "[".$clave."] ".$valor."\n";
?>

Los equivalentes a estas construcciones con un bucle for serían:

<?php
for (reset($array); list(, $valor) = each($array); )
  echo $valor."\n";
?>

y

<?php
for (reset($array); list($clave, $valor) = each($array); )
  echo "[".$clave."] ".$valor."\n";
?>

respectivamente

Break y continue

Break y continue son palabras reservadas que sirven para alterar la ejecución de un bucle.

Break sirve para cortar la ejecución de un bucle, tras ejecutar esta sentecia el bucle finalizará su ejecución y se ejecutarán las instrucciones a continuación del bucle. Desde un bucle interno podemos interrumpir la ejecución de bucles anidados externos, esdecir, si tubieramos dos bucles for anidados, usando "break 2;" desde el bucle interno, detendriamos la ejecución de ambos bucles.

Continue sólo corta la iteración actual del bucle, pero no detiene la ejecución del mismo, cuando usemos este instrucción, el bucle dejara de ejecutarse con el valor actual y empezará a ejecutarse con el siguiente valor (si lo hubiere). De la misma manera que break, continue puede usarse desde bucles anidados para interrupir la ejecución de bucles más externos.

Algun ejemplo de uso puede ser:

Buscar una posicion de un array

<?php
for ($i=0; $i<count($array); $i++) // Recorremos el array
  if ($array[$i] == $busqueda) // Si encontramos el elemento...
    break; // Paramos el array y $i tendrá la posición del elemento encontrado
if ($i < count($array)) // Comprobamos que haya encontrado algo
  echo "Elemento encontrado en la posición: ".$i;
else
  echo "Elemento no encontrado";
?>

Imprimir los número pares menores de 100, una manera cutre pero que sirva de ejemplo :P

<?php
for ($i=0; $i<100; $i++) {
  if ($i%2 != 0)
    continue;
  echo $i."\n";
}
?>

Bucles infinitos

Un bucle infinito no es ningun tipo especial de bucle, simplemente es un bucle cuya condición nunca se hace false, con lo que su ejecución es (teóricamente) infinita. Suelen ser frutos de un bucle mal construido.

Algún ejemplo de bucle infinito puede ser

<?php
while (true)
  echo "Soy un bucle infinito\n";

for (;;)
  echo "Yo también\n";
?>

Bucle infinito?

<?php
for ($i=0; $i<1000; $i=rand(0, 1000))
  echo "Soy un bucle, puedo ejecutarme muchas veces pero por estadística algún día dejaré de ejecutarme... Aunque puede que no :P\n";
?>

Hacer un bucle infinito, y luego detener su ejecución con un break suele ser una chapuza, si necesitas hacer esto seguramente la condicion del break sea lo que necesitas para construir la condición del bucle.

Conclusiones

Me gustaría repasar las conclusiones obtenidas, sobre todo para esa gente que usa el while para todo:

  • Hay mas bucles que el while, usalos que para algo están.
  • Las llaves en los bucles sólo son necesarias si tienes mas de una instrucción, no llenes tu código de llaves innecesarias.
  • Si necesitas un while que se ejecute al menos una vez: usa un do/while. Dar un valor inicial a una variable para que entre en un while es una chapuza
  • Si necesitas un while con un contador lo que buscas es un bucle for
  • Si necesitas recorrer un array usa foreach

Resumen

Propiciado por el mal uso que hace la gente de los bucles que he visto en muchas ocasiones, me dispuse a redactar este documento en el que explico la construcción de los bucles, su funcionamiento y en qué situaciones debemos usar cada uno.

Índice

  1. Bucles en PHP

Otros artículos