Esta é uma versão arquivada/estática do antigo Blog do André. Isso significa que todo o conteúdo aqui presente não irá ser atualizado, e pode conter erros. Algumas funcionalidades poderão não estar disponíveis nesta versão arquivada.
14
Ago 2010

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:

  1. 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.
  2. 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

Alguma dúvida, opinião, crítica ao artigo? Deixem um comentário, estou aqui para vos ouvir! Até mais ;)

Gostou deste artigo?

Facebook Twitter Google Plus Delicious

2 Comentários

 

  • Gravatar de Guilherme

    Guilherme

    20/10/2011 @ 17:03

    Vlw andré… ajudou muito!

    • Gravatar de André

      André

      26/10/2011 @ 15:32

      Obrigado Guilherme, ainda bem que foi útil!