Cabeceras: la función header()

La función header()

Como se explica en la lección sobre el protocolo HTPP, cuando un servidor envía una página web al navegador, no sólo envía la página web, sino también información adicional (el estado y los campos de cabecera). Tanto el estado como los campos de cabecera se envían antes de la página web.

Normalmente, un programa PHP sólo genera la página web y es el servidor el que genera automáticamente la información de estado y los campos de cabecera y los envía antes de enviar el contenido generado por el programa. Pero un programa PHP también puede generar la información de estado y los campos de cabecera, mediante la función header().

Pero la función header() no debe utilizarse en un programa una vez se ha generado contenido HTML (por ejemplo, con un print). El motivo es que si un programa genera contenido HTML, el servidor genera automáticamente la información de estado y los campos de cabecera y a continuación envía el contenido generado. Si después el programa contiene una instrucción header(), se produce un error porque las cabeceras ya se han enviado, como muestran los dos ejemplos siguientes.

En el primer ejemplo, se llama a la función header() después de un print, lo que provoca un aviso de error.

<?php
print "<p>Intento fallido de redirección</p>\n";
header("Location:http://www.example.com");
?>

Intento fallido de redirección

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

En el segundo ejemplo, se llama a la función header()en un programa que ya ha generado contenido antes del fragmento PHP, lo que provoca un aviso de error:

<p>Intento fallido de redirección</p>
<?php
header("Location:http://www.example.com");
?>

Intento fallido de redirección

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

Por ello, hay que tener mucho cuidado en no generar ningún tipo de salida antes de utilizar la la función header() ni en el programa, ni en las bibliotecas a las que se llame en el programa.

Hay que tener en cuenta que una simple línea en blanco antes del bloque PHP sería suficiente para impedir el envío de la cabecera:

<?php
header("Location:http://www.example.com");
?>

Warning: Cannot modify header information - headers already sent by (output started at ejemplo.php:2) in ejemplo.php on line 3

Nota: Otra situación que puede provocar el fallo de la función header() es cuando el programa contiene un error antes, ya que PHP generará un aviso de error que es también parte de la salida del programa y eso provocaría el fallo de la función header() posterior. Esta situación suele darse al desarrollar el programa, ya que normalmente los servidores de desarrollo están configurados para mostrar el mayor número posible de avisos de error, pero desaparece en los servidores de producción, que se suelen configurar para no enviar al usuario ningún mensaje de error.

Buffer de salida

El error que se ha comentado en el apartado anterior puede no llegarse a producir si el servidor está utilizando un buffer de salida. Un buffer de salida almacena la salida del programa, que no se envía al navegador hasta que se completa la página o se llena el buffer y se puede definir mediante la directiva output_buffering.

Si cuando el programa llega a la instrucción header(), el contenido generado anteriormente todavía no se ha enviado al navegador porque ha cabido en el buffer de salida, el servidor puede todavía modificar la información de cabecera y la función header() no daría error.

El uso de un buffer de salida puede así enmascarar errores de programación como los de los ejemplos anteriores. Por ese motivos, en la lección de configuración de XAMPP se recomienda desactivar el buffer de salida al desarrollar el programa, de manera que al probar el programa detectemos este tipo de errores.

Si por algún motivo un programa necesita que haya un buffer de salida, para no tener que depender de la configuración del servidor el programa puede gestionar directamente el buffer mediante la función ob_start() y ob_end_flush(). Estos buffers son temporales y desaparecen cuando se termina de ejecutar la página.

Los ejemplos siguientes muestran la influencia del buffer.

Marca de orden de bytes (BOM) de UTF-8

Otra situación que puede causar problemas en los programas que incluyen la función header() es la marca de orden de bytes (BOM) de los archivos UTF-8. Como en el caso anterior, la utilización de un buffer de salida puede enmascarar este problema.

La marca de orden de bytes son los primeros caracteres de un fichero UTF. Estos caracteres indican:

En el caso de los archivos UTF-8, la marca de orden de bytes son los tres caracteres EF BB BF (que corresponden a los caracteres ). Pero como en un archivo UTF-8 no se necesita indicar el orden de los bytes, puesto que cada carácter ocupa solamente un byte, la marca es opcional, es decir, que hay archivos UTF-8 con BOM y archivos UTF-8 sin BOM. Los editores (como Eclipse for PHP developers) no muestran estos caracteres y suelen respetarlos al guardar de nuevo al archivo. Para ver esos caracteres, se puede utilizar un editor hexadecimal, que muestra todos los caracteres sin excepción.

Las imágenes siguientes muestran los valores hexadecimales de dos archivos UTF-8 (con y sin BOM ), utilizando el editor Notepad++ con el plug-in HEX-Editor:

archivo UTF-8 sin BOM

archivo UTF-8 con BOM

Esta marca de orden de bytes puede causar problemas si PHP no reconoce la marca de orden de bytes como un identificador de juego de caracteres, sino como caracteres situados antes de un framento PHP que se deben enviar al navegador. El comportamiento de PHP depende de las opciones elegidas al compilar el intérprete de PHP, por lo que la única solución viable es asegurarse de que los archivos no tienen BOM.


Los ejemplos siguientes muestran la influencia del BOM cuando no hay buffer de salida.


Notepad++ permite quitar la marca de orden de bytes de un archivo php mediante el menú Codificación > Codificar en UTF-8 sin BOM:

Quitar BOM con Notepad++

Para eliminar las marcas de orden de bytes de un conjunto grande de archivos php, se puede utilizar este script de Linux:

#! /bin/bash

find ./ -name "*.php" -type f | while read file
do
  if [[ -f $file && `head -c 3 $file` == $'\xef\xbb\xbf' ]]; then
      # si el fichero existe y tiene BOM UTF-8
      mv $file $file.bak
      tail -c +4 $file.bak > $file
      echo "BOM eliminado en $file"
  fi
done

Redirecciones: header("Location:...")

La función header() se puede utilizar para redirigir automáticamente a otra página, enviando como argumento la cadena Location: seguida de la dirección absoluta o relativa de la página a la que queremos redirigir.

<?php
header("Location:http://www.example.com/");
?>

Si además de redirigir a una página, se quieren enviar controles a dicha página, se pueden añadir a la cadena separando los controles con el carácter &.

Nota: En este caso no se debe utilizar la entidad de carácter &amp;

<?php
header("Location:http://www.example.com?nombre=Pepito&edad=25");
?>

Si el valor a enviar contiene espacios, se pueden escribir caracteres + o espacios para separar las palabras:

<?php
header("Location:http://www.example.com?nombre=Pepito+Conejo&edad=25");
?>
<?php
header("Location:http://www.example.com?nombre=Pepito Conejo&edad=25");
?>

Resto del programa tras la redirección

La ejecución de un programa no se detiene al encontrar una redirección, sino que PHP ejecuta el programa hasta el final. Dependiendo de las instrucciones que haya tras la dirección, el resultado puede ser relevante o no.

Si tras una redirección no queremos que se ejecute el resto del programa, se debe escribir el programa de manera que se ejecuten unas instrucciones u otras, mediante expresiones if ... else ... . El programa siguiente realizará una redirección u otra en función de la condición que se escriba:

<?php
if (condicion) {
    header("location:http://www.mclibre.org/");
} else {
    header("location:http://www.example.com/");
}
?>

Un ejemplo práctico de uso de redirecciones es el siguiente. Supongamos que tenemos un formulario (confirmar.php) para confirmar una acción destructiva (borrar datos de una base de datos, por ejemplo) que envía la confirmación a destruir.php. Para tener en cuenta el caso de que un usuario acceda directamente a la página destruir.php sin haber confirmado la acción en confirmar.php, podemos hacer que se compruebe si se ha recibido la confirmación y en caso contrario redirigir a la página principal del sitio.

<?php
// destruir.php
if (!isset($_REQUEST["si"])) {
    header("Location:index.php");
} else {
    ...
}
?>

Para simplificar este tipo de programas se puede utilizar exit, que detiene el programa en ese punto.

Nota: exit no es una función, sino una palabra reservada del lenguaje, pero también se puede utilizar seguida de paréntesis como si fuera una función: exit(). Se puede incluso incluir un valor numérico en los paréntesis, que sería el valor que devolvería el programa.

El ejemplo anterior se podría escribir entonces de la siguiente manera, sin necesidad de incluir las instrucciones posteriores en un bloque else ...:

<?php
if (condicion) {
    header("location:http://www.mclibre.org/");
    exit;
}
header("location:http://www.example.com/");
?>

El inconveniente de exit es que detiene la ejecución del programa, pero también la de los programas que hubieran llamado a esos programas, como se muestra en los ejemplos siguientes:

El programa ejemplo_1.php llama al programa ejemplo_2.php y el resultado incluye las instrucciones generadas por ambos programas. Es decir, si se ejecuta el programa ejemplo_1.php, el resultado final será una redirección a la página web de mclibre.

<?php
// ejemplo_1.php
include "ejemplo_2.php";
header("location:http://www.mclibre.org/");
?>
<?php
// ejemplo_2.php
header("location:http://www.example.com/");
?>

Pero si el segundo programa se añade exit, se interrumpiría tanto el segundo como el primero. Es decir, si se ejecuta el programa ejemplo_1.php, el resultado final será una redirección a la página http://www.example.com/.

<?php
// ejemplo_3.php
include "ejemplo_4.php";
header("location:http://www.mclibre.org/");
?>
<?php
// ejemplo_4.php
header("location:http://www.example.com/");
exit;
?>

Crear con PHP otros tipos de archivos

PHP puede generar archivos de todo tipo, pero para que el navegador reconozca el tipo de archivo se debe enviar el tipo MIME correspondiente. Eso se para ello, se puede utilizar la función header() enviando como argumento la cadena Content-type: seguida del tipo MIME correspondiente.

Existen muchos tipos MIME (véase lista de tipos MIME en MDN).

Los tipos MIME más habituales son:

extensión del archivo tipo de archivo tipo MIME
.css hoja de estilo CSS text/css
.html página web HTML text/html
.js JavaScript application/js
.svg SVG image/svg+xml

El programa siguiente crea una imagen SVG, concretamente un cuadrado:

<?php
header("Content-type: image/svg+xml");
print "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" \n"
    . "    width=\"100\" height=\"100\" viewBox=\"-5 -5 100 100\">\n";
print "  <rect fill=\"none\" stroke=\"black\" stroke-width=\"1\" "
        . "x=\"5\" y=\"5\" width=\"90\" height=\"90\" />\n";
print "</svg>";
?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
    width="100" height="100" viewBox="-5 -5 100 100">
  <rect fill="none" stroke="black" stroke-width="1" x="5" y="5" width="90" height="90" />
</svg>

Ese programa se puede llamar desde otro programa para generar la imagen:

<p><img src="imagen.php" alt="cuadrado" /></p>