Erro “Headers already sent” desvendado
Quem programa em PHP e nunca se deparou com este erro? Pois é. Mais cedo ou mais tarde, acaba por acontecer, quer seja por descuido, quer seja por uma solução mal desenhada. É provavelmente um dos erros mais frequentes no PHP, mas que pode ser resolvido de forma muito rápida e sem complicações. Neste artigo, vamos abordar algumas estratégias para resolver o problema e compreender porque é que ele aparece, tudo em 3 simples passos.
1. Analisar a mensagem de erro
O primeiro passo é analisar o erro que nos é dado (não está lá apenas para desmotivar…). A mensagem de erro tem o seguinte aspecto:
Warning: Cannot modify header information - headers already sent by (output started at /var/www/htdocs/script.php:123) in /var/www/htdocs/script.php on line 321
Daqui podemos extrair duas informações: em que linha o PHP começou a enviar conteúdos para o browser (vamos no fim ver porque é que isto é importante), e a linha que originou este erro, devido a uma tentativa de alterações dos cabeçalhos HTTP. No exemplo atrás, conseguimos extrair estas duas informações facilmente: a vermelho, temos o ficheiro e linha em que o output começou e a verde temos o local onde alguma função tentou alterar os cabeçalhos HTTP.
O que fazer então? Anotem o script e a linha da parte em vermelho entre parêntesis (ignorem para já a parte em verde), onde o output começou (no exemplo, a notação script.php:123 significa que o output começou no ficheiro script.php, na linha 123 (e não no porto 123 como alguns poderão pensar, não tem nada a ver com redes). Abram o ficheiro nessa linha, e avancem para o passo seguinte.
2. Caçar outputs indesejados
O que queremos procurar agora são caracteres que são enviados para o browser. Basicamente, estamos à procura de qualquer caractere incluindo:
- Quebra de linha (rn ou n)
- Espaços em branco
- Byte Order Mask (BOM) (de forma grosseira, um número hexadecimal no início de cada ficheiro para indicar a sua codificação)
- Echo, print, etc, antes de funções que alterem cabeçalhos HTTP (header(), setcookie(), session_start(), …)
Onde procurar? Dentro das zonas de PHP (delimitadas por <?php e ?>) não há qualquer problema com estes caracteres, mas fora destas zonas é interpretado como parte da resposta, logo é aí que importa remover esses caracteres. Na tarefa, utilizem um editor de texto que permita ver caracteres ocultos: o Notepad++ para Windows tem essa capacidade. Removam os caracteres indicados que estejam fora da zona de PHP, e tentem novamente correr o script.
Deu o mesmo erro numa linha diferente? Óptimo. Agora é um processo recursivo: voltem ao primeiro passo, analisem a mensagem e corrijam o próximo caractere. Já não dá erros? Ainda melhor! Vamos então perceber o porquê de tudo isto.
3. A derradeira resposta
Todos estes erros em torno dos cabeçalhos HTTP tem uma explicação simples: os dados enviados entre browser ->servidor web -> browser chamam-se pedidos e respostas HTTP respectivamente. As respostas são enviadas do servidor web para o browser, e são compostas por 2 partes:
- cabeçalho HTTP, onde vão diversas informações, como o tipo de resposta, tamanho do documento, cookies que o cliente (browser) deve guardar, etc.
- resposta/dados, que tipicamente é a página visualizada no browser (ou de forma mais directa, todo o conteúdo que é visível na opção “Ver código fonte” de um browser)
Estas partes não se podem trocar entre si, isto é, o cabeçalho HTTP devem vir estritamente antes da resposta, separado desta por duas quebras de linha (vejam todos os detalhes técnicos no RFC 2616). Então qual o problema? Um caractere é suficiente para o servidor ser forçado a enviar uma resposta para o browser (é interpretado como resposta/dados). Como vimos, o cabeçalho vem antes dos dados, e uma vez enviado, já não pode ser alterado.
As funções header(), setcookie(), session_start() necessitam de alterar o cabeçalho HTTP, e uma vez que este já foi enviado (de forma forçada, uma vez que havia caracteres indesejados nas páginas), a única coisa que podem fazer é enviar um erro.
Outras alternativas
Existe outra maneira de conseguir resolver o caso: usando as funções ob_start e ob_end_flush. Estas funções permitem criar um buffer temporário, cujo objectivo é guardar tudo o que for enviado a partir da criação do buffer (chamada à função ob_start()) até decidirmos quando deve ser enviado para o browser (chamada à função ob_end_flush()).
Desta forma, nenhum conteúdo de resposta/dados é enviado para o browser, e podemos alterar os cabeçalhos livremente. Deixo aqui um exemplo:
<?php
ob_start();
echo "Isto está a escrever para o documento, mas ainda não foi enviado ";
echo "porque activámos o buffer com a função anterior (ainda está tudo em memória)";
session_start();
header("X-COMMENT: Alteramos o cabeçalho com isto");
setcookie("bolacha", "E ainda definimos um cookie!");
ob_end_flush();
// Após a chamada a esta função, o buffer é descarregado e
// todos os conteúdos enviados para o browser (cabeçalhos + resposta)
?>
Embora interessante, esta opção tem alguns inconvenientes:
- se pretendemos redireccionar em qualquer situação, estamos a encher um buffer desnecessariamente para no fim nunca ser utilizado (o cliente vai carregar o novo endereço que recebeu no cabeçalho da resposta, e vai descartar todo restante conteúdo)
- Não resolve o problema do BOM, porque este vem sempre no início do ficheiro, antes das tags de PHP.
Outra solução passa por activar o buffering automático no PHP. No fichiero PHP.ini, localizem a linha
output_buffering = Off
e substituam por
output_buffering = On
À semelhança da solução anterior, é criado um buffer que guarda os dados. A diferença é que este buffer é gerido automaticamente pelo PHP, e não necessita de chamada a funções. O buffer cobre todo o documento, incluindo assim o BOM, e logo, também resolve este problema.
Ligações interessantes
- RFC 2616 Section 6
- Visualizador de pedidos e respostas HTTP
- Resumo em inglês sobre aspectos técnicos do protocolo HTTP
- Editores de texto capazes de lidar com caracteres ocultos
- PHP: header – Manual (contém informações sobre os cabeçalhos e respostas)
- Mais informações sobre a opção “output_buffering” do PHP
Alguma dúvida, opinião, crítica ao artigo? Deixem um comentário, estou aqui para vos ouvir! Até mais
2 Comentários