Output Buffer do PHP e os mistérios do echo (parte 1)

Artigo que mostra como controlar o buffer do PHP (Output Buffer) e utilizá-lo em aplicações práticas como modificar o conteúdo já enviado para o buffer.

Introdução

Um dos comandos mais conhecidos do PHP é o echo, que "simplesmente" envia uma string para a saída padrão (STDOUT). Porém, o funcionamento aparentemente simples de um echo pode envolver conceitos bem mais complexos, como o output buffer e a transferência de pacotes HTTP de resposta do servidor para o cliente.

Neste post veremos alguns conceitos necessários para compreender a fundo o funcionamento do Output Buffer. Também veremos um exemplo inicial de como usufruir deste recurso para modificar dados que já foram "impressos" no PHP. No próximo post, veremos alguns exemplos mais complexos e suas aplicações práticas.


O que é Output Buffer

Primeiramente, precisamos saber o que é "buffer". Buffer, na computação, é um espaço na memória usado para guardar bytes (dados) para posterior utilização e/ou processamento. Uma analogia interessante que podemos fazer é com uma caixa d'água em uma casa. Ela permite acumular uma quantidade de água para ser utilizada no futuro, quando alguém da casa abre uma torneira, chuveiro, etc.

Um "output buffer" é um buffer específico para armazenar dados que serão enviados para o output (saída de dados). O output pode variar, de acordo com o contexto que estamos trabalhando. Se estamos usando PHP para criar um script para ser executado em terminal (php-cli), o output normalmente é a tela do terminal. Mas se estamos usando PHP para criar um script para responder a uma requisição HTTP no ambiente web, então o output representa o pacote HTTP de resposta do servidor para o cliente. Ou seja, "executar um echo" no ambiente web não significa imprimir algo no navegador, mas sim incluir uma string em um pacote HTTP que será enviado para o cliente para posterior renderização.

Portanto, quando falamos de output buffer, estamos falando de um container que irá armazenar informações antes de jogar no output final.


A diretiva HTTP Transfer-Encoding

Quando um navegador (cliente) solicita uma URL específica, o servidor pode devolver um pacote HTTP com, por exemplo, um documento HTML. O servidor pode enviar este documento de uma só vez (todo conteúdo do arquivo) ou em partes (cada parte em um momento diferente ou todas partes de uma vez).

A diretiva HTTP "Transfer-Encoding" especifica a forma como o conteúdo do pacote HTTP será enviado. Ela pode valer: chunked, compress, deflate, gzip, identity. O valor "identity" é o padrão e não precisa ser informado. Ele especifica que o documento será enviado por completo de forma binária. Os valores "compress", "deflate" e "gzip" especificam que o valor será enviado por completo, mas comprimido com um algorítimo de compressão. E o valor "chunked" é que especifica que o valor será enviado em partes. Neste caso, o servidor envia o cabeçalho HTTP, depois envia parte por parte. Para enviar uma parte, o servidor envia primeiro o tamanho da parte (em hexadecimal) seguido de uma quebra de linha (CRLF), depois o conteúdo da parte. O cliente vai recebendo parte por parte e "montando" o arquivo final. Em algumas situações, o navegador pode optar por renderizar parte do que já foi enviado enquanto continua recebendo mais partes do documento. Isso pode dar a impressão de que o documento foi carregado mais rápido do que realmente foi. Por outro lado, se as partes são muito pequenas, uma nova parte pode afetar o layout, que precisa ser redesenhado, tornando a exibição da página mais complexa pelo navegador.


O Básico sobre Output Buffer

Conhecendo os conceitos básicos de output buffer, vamos montar um exemplo de como controlar esse buffer e, também, modificá-lo.

Neste primeiro exemplo, vamos gerar um documento em buffer com o conteúdo "Olá PHP", e depois modificar o buffer para que ele se torne "Olá Rubens":

<?php
// Iniciar o buffer
ob_start();

// Imprimir o documento
echo 'Olá PHP';

// Obter o conteúdo do buffer e encerrá-lo
$conteudo = ob_get_clean();

// Modificar o conteudo do buffer
$conteudo = str_replace('PHP', 'Rubens', $conteudo);

// Exibindo o conteudo novo
echo $conteudo;

Explicando: (1) Abrimos o buffer com a função ob_start. A partir deste ponto, tudo que é impresso para o output vai, na verdade, para o buffer. (2) Após imprimir um conteúdo, que foi para o buffer, obtemos o conteúdo do buffer e jogamos em uma variável com a função ob_get_clean, além disso, a função encerra o buffer, ou seja, os comandos de impressão voltam a ser jogados para a saída padrão ao invés de serem redirecionadas para o buffer. (3) Modificamos o conteúdo da variável e imprimimos o novo conteúdo na saída padrão, mas, desta vez, como não havia nenhum buffer ativo, a saída foi diretamente para a saída padrão.

Ao invés de usar a função ob_get_clean, poderiamos usar ob_get_contents para obter o conteúdo do buffer (sem encerrar o buffer) e depois ob_end_clean para encerrar o buffer. A vantagem da função ob_get_clean é para poupar memória, pois se jogarmos o conteúdo do buffer em uma variável e não encerrarmos o buffer, o conteúdo do buffer estará duplicado na memória (na variável $conteúdo e no próprio buffer).


Aplicação prática

Neste post, vimos apenas um exemplo simples de uso do output buffer. A aplicação prática deste recurso pode ser, por exemplo, para modificar a codificação do documento gerado (após gerá-lo por completo). Ou então, por exemplo, incluir um arquivo CSS ou JavaScript no cabeçalho de um documento que já foi gerado. Para isso, seria necessário localizar o ponto em que se gostaria de incluir o trecho de código HTML adicional e adicioná-lo manualmente.

Aguarde o próximo post para aprender conceitos e aplicações mais avançados sobre o output buffer.

10 comentários

Everton disse...

Muito boa explicação! Tenho uma dúvida sobre a aplicação do output buffer junto com um redirecionamento através do "header('Location: http://link');".
Um redirecionamento desse tipo apresenta um erro no PHP (não lembro exatamente qual agora, pois não tenho como testar no momento) se for feito no meio do código. Quer dizer, se as informações do header já tiverem sido preenchidas pela aplicação, aparentemente não é possível enviar um redirecionamento desses.
Isso entretanto funciona com o uso de um output buffer, o redirecionamento acontece normalmente.

Usar o output buffer nesse caso é correto?
Existe uma alternativa para um redirecionamento desses (normalmente utilizado no submit de um form)?

Abraços!

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá Everton,

O erro que você se refere é o "Headers already sent by ...". O problema ocorre pois para enviar um comando HTTP "Location" (para fazer o redirecionamento), é preciso que não seja enviado nenhum conteúdo com a requisição HTTP. Se você faz um echo antes, então o pacote HTTP já vai ter conteúdo.

Usar o output buffer para resolver este problema é uma solução. Não vou dizer que é a melhor forma de se fazer, mas é uma solução viável quando se tem um sistema que realiza vários "echo" e você precisa optar por redirecionar. O ideal é que você trate as condições do redirecionamento antes de chamar qualquer "echo".

Normalmente, no modelo MVC, é realizado um único "echo". A view gera todo o documento, então ele é despejado para o output. Neste caso, fica mais fácil de controlar os redirecionamentos, pois o controller é responsável por encaminhar o usuário para o lugar certo e só chamar a view quando já se tem certeza que ela deve ser usada.

Unknown disse...

Excelente artigo! Adoro os temas que você aborda, parabéns!
Lendo o comentário acima fiquei com dúvida, pois não consegui reproduzir o erro citado (Headers already send by...). O que eu fiz foi dar vários echo em um documento e depois usar o header location, mas funcionou normalmente. Você pode me dizer como eu reproduzo esse erro, pois quero compreender bem isso. Obrigado pela atenção.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Michael.

Esse erro ocorre quando não está sendo utilizado nenhum buffer.

Na parte 2 deste artigo (http://rubsphp.blogspot.com.br/2012/08/php-output-buffer-e-os-misterios-do-echo-parte2.html) eu comento que é possível utilizar o buffer em um nível mais global, especificado no arquivo de configuração php.ini. Ou seja, ao iniciar o PHP, você já está utilizando um buffer, mesmo sem chamar ob_start.

Por isso, se ele estiver habilitado, não vai ocorrer o erro pois o conteúdo foi para o buffer e não foi ainda para o cliente.

Leia o próximo artigo e veja se fica mais claro.

Unknown disse...

Perfeito! Li a segunda parte do artigo, defini como off a diretiva output_buffering no php.ini e o erro foi reproduzido. Agora tive uma melhor compreensão do assunto.
Você tem uma didática excepcional. Parabéns!