No artigo anterior, vimos uma introdução sobre o Output Buffer do PHP, com seus conceitos básicos e uma aplicação prática do recurso. Neste artigo, veremos a continuação do assunto, nos aprofundando em conceitos mais complexos e algumas aplicações práticas mais avançadas.
O Output Buffer como Callback
No exemplo mostrado no post anterior, vimos como abrir e fechar um bloco para ser controlado com o output buffer, que são as funções ob_start e ob_get_clean (ou obter o conteúdo com ob_get_content e depois fechar o bloco com ob_end_clean).
Porém, a função ob_start pode funcionar com uma função de callback, que é executada ao encerrar o buffer ou ao realizar o flush. Este evento pode se dar de forma explícita, com a chamada de alguma função (como ob_flush ou ob_end_clean) ou, simplesmente, quando o script terminar a execução e o buffer é automaticamente fechado e jogado para flush.
A função de callback recebe por parâmetro o conteúdo do buffer (uma string) e deve retornar esta string com as modificações desejadas ou retornar false para que o conteúdo original seja usado sem modificações.
A linguagem PHP já oferece algumas funções que podem ser usadas como callback para ob_start, veja algumas:
- ob_ gzhandler - Para comprimir o conteúdo antes de enviar para o cliente.
- ob_iconv_handler - Para trocar a codificação do documento antes de enviar para o cliente (usando a extensão "iconv").
- mb_output_handler - Para trocar a codificação do documento antes de enviar para o cliente (usando a extensão "multibyte string").
Podemos usar uma função pronta do PHP, mas também podemos fazer nossa própria função. Por exemplo, vamos fazer uma função que troca a letra "a" pela letra "b" em todo o documento, antes de enviá-lo para o cliente.
<?php ob_start('trocar_a_por_b'); echo 'atenção para a alteração'; exit(0); function trocar_a_por_b($documento) { return str_replace('a', 'b', $documento); }
Note que não foi preciso fechar o controle de buffer. Quando chega-se na instrução exit, o PHP sabe que precisa chamar a função callback para o buffer armazenado.
Para enviar um documento comprimido para o cliente, basta usar o callback de ob_gzhandler:
<?php ob_start('ob_gzhandler'); echo 'Conteúdo do documento'; exit(0);
Você pode notar que o documento foi trafegado de forma compactada usando, por exemplo, o plugin firebug do Firefox. Com ele, você vê que na aba "Rede", o cabeçalho de resposta do servidor veio com a diretiva "Content-Encoding: gzip".
E para trocar a codificação de um arquivo, vamos ver como fazer usando a extensão "iconv":
<?php iconv_set_encoding('internal_encoding', 'UTF-8'); iconv_set_encoding('input_encoding', 'UTF-8'); iconv_set_encoding('output_encoding', 'ISO-8859-1'); ob_start('ob_iconv_handler'); header('Content-type: text/html; charset=UTF-8'); echo 'Mudando a codificação'; exit(0);
Neste caso o meu arquivo foi gerado em UTF-8, mas o documento enviado para o cliente será convertido para ISO-8859-1. Observe que é necessário configurar estas codificações antes de usar o controle de buffer.
Observação: a própria função de callback modifica o cabeçalho HTTP "Content-type" para ajustar a codificação. Porém, se você especificar o charset usando meta tag, a função não é capaz de mudar.
O funcionamento do flush
O flush é um mecanismo para descarregar um conteúdo do buffer e aplicar o tratamento especial, caso necessário. Por exemplo, queremos trocar a letra "a" por "b" em todo documento, mas queremos fazer isso de tempos em tempos ao invés de fazer tudo de uma vez. Veja como ficaria o exemplo inicial:
ob_start('trocar_a_por_b'); echo 'atenção '; ob_flush(); echo 'para a '; ob_flush(); echo 'alteração'; exit(0); function trocar_a_por_b($documento) { return str_replace('a', 'b', $documento); }
Neste caso, a cada vez que chamamos a função ob_flush, estamos dizendo para o PHP processar o que está em buffer e já descarregar para o pacote HTTP que será entregue ao cliente. Isso pode ser útil caso a sua função precise de muita memória para realizar a operação.
Uma outra forma de executar a função de callback aos poucos, seria especificar um tamanho mínimo para processamento. Assim, quando o buffer atinge o tamanho definido, a função de callback é chamada para processar parte do documento. Isso é feito na própria chamada da função ob_start. Por exemplo, vamos chamar a função de callback quando o buffer atingir o tamanho mínimo de 15 bytes:
<?php ob_start('trocar_a_por_b', 15); echo 'atenção '; echo 'para a '; echo 'alteração'; exit(0); function trocar_a_por_b($documento) { return str_replace('a', 'b', $documento); }
Ao realizar o primeiro echo o tamanho do buffer ainda não chegou a 15, mas depois do segundo echo, o buffer passa deste tamanho, então faz o processamento do buffer, o descarrega para o pacote HTTP e limpa o buffer. Depois do terceiro echo o buffer ainda não chega a ter 15 bytes, mas como chega ao final do script (chamada do exit), então o buffer é processado com o "resto" do documento.
Controle de Output Buffer em Cascata
O controle de output buffer em cascata consiste em um determinado controle fazer seu processamento sobre o resultado do processamento de outro controle. Ou seja, são definidos níveis de processamento e um nível mais externo precisa esperar pelo resultado . Por exemplo, podemos querer converter a codificação de UTF-8 para ISO-8859-1 e, depois, compactar o documento com o gzip:
<?php iconv_set_encoding('internal_encoding', 'UTF-8'); iconv_set_encoding('input_encoding', 'UTF-8'); iconv_set_encoding('output_encoding', 'ISO-8859-1'); ob_start('ob_gzhandler'); ob_start('ob_iconv_handler'); header('Content-type: text/html; charset=UTF-8'); echo 'Mudando a codificação'; exit(0);
Observe que o controle de compactação com gzip foi chamado antes do controle de conversão de UTF-8 para ISO-8859-1. Isso significa que ele está no nível mais externo ou, em outras palavras, o controle de conversão de codificação está "dentro" do controle de compressão. Talvez se incluirmos algumas chaves e indentação, a o funcionamento fique um pouco mais claro:
<?php iconv_set_encoding('internal_encoding', 'UTF-8'); iconv_set_encoding('input_encoding', 'UTF-8'); iconv_set_encoding('output_encoding', 'ISO-8859-1'); ob_start('ob_gzhandler'); { ob_start('ob_iconv_handler'); { header('Content-type: text/html; charset=UTF-8'); echo 'Mudando a codificação'; } } exit(0);
Com isso, você pode incluir quantos níveis de controle de output buffer desejar. Mas tome cuidado pois sua aplicação pode ter a performance comprometida se você incluir muitos controles em cascata, além de eles poderem gastar mais memória que o esperado.
Para saber em que nível de controle de output você está em determinado momento, você pode usar a função ob_get_level, sendo que o nível "0" (zero) é o mais abrangente (processado por último).
Configurando o nível de controle de output buffer global
A linguagem PHP também oferece uma forma de se definir um nível global de controle de output buffer. Isso é definido via diretiva de configuração. Por exemplo, se configurarmos a diretiva output_handler do php.ini com o valor "ob_gzhandler", significa que todos os scripts PHP terão um nível de controle de output buffer global automaticamente, que fará a compressão do documento gerado. Ou seja, corresponde a incluir a linha abaixo automaticamente em todos os scripts PHP:
ob_start('ob_gzhandler');
No caso do callback "ob_iconv_handler", seria necessário configurar as diretivas de configuração "iconv.input_encoding", "iconv.internal_encoding" e "iconv.output_encoding", no próprio arquivo php.ini, que faz presumir que todos arquivos PHP terão a mesma codificação.
A diretiva output_buffering serve para definir se haverá um controle de buffer global antes de despachar o conteúdo para o pacote HTTP. Ela pode assumir os valores:
- on: para habilitar o buffer de forma ilimitada (primeiro todo conteúdo vai para o buffer, depois é gerado o pacote HTTP de resposta, depois o pacote é enviado para o cliente.
- off: para não ter controle de buffer global (toda vez que você chama a função flush, o conteúdo é enviado para o pacote HTTP e enviado o pedaço para o cliente.
- um número de bytes: para habilitar o buffer que é despachado ao atingir o número de bytes especificado.
Observação: a diretiva output_buffering não pode ser definida em php-cli.
Transferindo o documento em partes
No post anterior, vimos que um pacote HTTP pode ser enviado de uma vez só para o cliente ou ser enviado em pequenos pacotinhos, que são unidos no cliente para formar o documento completo (veja o cabeçalho HTTP "Transfer-Encoding").
Agora que conhecemos os principais conceitos acerca do output buffer, podemos conhecer como enviar um documento aos poucos.
Primeiramente, não é necessário especificar o cabeçalho HTTP "Transfer-Encoding: chunked" pois isso será feito automaticamente pelo PHP.
Basicamente, para transferir o documento em partes, precisamos estar no nível mais global do output, ou seja, não estar usando nenhum output buffer. Neste ponto, se utilizarmos a função flush, o conteúdo desde o último flush será enviado para o cliente em um pacotinho de dados. Vamos ver um exemplo:
// Considerando que a diretiva "output_buffering" esteja desabilitada (off) echo 'a'; flush(); sleep(5); echo 'b'; flush(); sleep(5); echo 'c'; exit(0);
Note que este script só vai funcionar se o output buffer não estiver definido no php.ini, ou seja, a diretiva output_buffering deve valer "off". Ou então, se estiver habilitada, o script precisa incluir a linha ob_end_clean(); logo no início do script, pois ele irá cancelar o output buffer mais global.
Se acessarmos este script no firefox, por exemplo, veremos o navegador exibir a letra "a", depois de 5 segundos exibir a letra "b" e depois de 5 segundos exibir a letra "c". O sleep foi colocado apenas para ficar notável que o conteúdo é recebido aos poucos.
Caso o buffer esteja habilitado, o documento será enviado em pedaços, mas eles serão enviados todos de uma só vez (quando todo o documento estiver pronto).
Se o buffer estiver habilitado, ainda há uma solução para enviar o documento em partes e as partes serem enviadas assim que chamarmos o flush. Basta chamarmos ob_flush para despachar o conteúdo do buffer e depois flush para despachar o conteúdo para o cliente:
// Considerando que a diretiva "output_buffering" esteja habilitada echo 'a'; ob_flush(); flush(); sleep(5); echo 'b'; ob_flush(); flush(); sleep(5); echo 'c'; exit(0);
2 comentários
ÓTIMO ARTIGO, está e uma parte muito importante do desenvolvimento web, que muitas vezes passa despercebida..
Excelente cara, me ajudou muito, continue assim : )
Postar um comentário
Nota: fique a vontade para expressar o que achou deste artigo ou do blog.
Dica: para acompanhar as respostas, acesse com uma conta do Google e marque a opção "Notifique-me".
Atenção: o blogger não permite inclusão de tags nos comentários, por isso, use algum site externo para postar seu código com dúvidas e deixe o link aqui. Exemplo: pastebin.com