Stream Filters

PHP utiliza o conceito de streams, como uma camada de abstração para IO. Leitura e escrita de arquivos pode ser feito com os streams. Sockets, também, podem ser lidos e escritos como streams. FTP e servidores HTTP tem suporte à stream. O Stream Filter nos permite executar operações em dados que estão sendo lidos ou gravados através de um stream.

O Stream nos permite fazer algo assim:

<?php  
    $conteudo = file_get_contents('http://url.com');
?>

O código acima recebe todo o conteúdo da página na URL de destino, e lê como se fosse um arquivo no sistema de arquivos local. Você também pode abrir arquivos compactados ou arquivo como Phar, bzip2, e arquivos GZIP, tendo o conteúdo descompactado em tempo de execução.

Stream Filters fornecem uma camada acima disso: Eles permitem que você abra um stream, e depois ter uma ou mais tarefas (filtros) são executados no stream de como os dados são lidos ou gravados para o stream.

Stream Filters na Prática

O que nós vamos fazer para praticar é ler um arquivo compactado com bzip2, re-codificar o arquivo de ISO-8859-1 para UTF-8, e em seguida transformá-lo em um arquivo onde os dados são capitalizados.

Dividindo nossas tarefas em uma sequência, vamos fazer o seguinte:

  • Abrir uma stream para leitura
  • Abrir uma stream para gravação
  • Descompactar o nosso arquivo
  • Transcodificar de ISO-8859-1 para UTF-8
  • Converter o conteúdo do stream para maiúsculas
  • Gravar o nosso conteúdo tratado em um arquivo de texto simples

Com o Stream Filter, isto é realizado através da criação de um par de streams, e depois atribuímos filtros para cada stream. Quando copiar os dados de um stream para outro, os filtros serão executados internamente.

Vamos começar com um arquivo teste.txt.bz2, que é um arquivo de texto ISO-8859-1 comprimido em bzip2 cujo conteúdo são os valores utilizados no post anterior separados por ponto e vírgula. E nós vamos gerar um arquivo chamado teste_fixed.txt.

Agora que tudo já foi explicado, vamos executar nossas tarefas passo a passo:

Nosso primeiro passo é criar nosso file handler, que servirá para manipularmos os arquivos, criaremos um com o arquivo de entrada e outro com o arquivo de saída.

<?php  
    $entrada = fopen('teste.txt.bz2', 'r');
    $saida   = fopen('teste_fixed.txt', 'wb');
?>

Apesar de, provavelmente, não acrescentar nada, o código acima, acaba com nossas duas primeiras tarefas foram executadas. Temos um file handler para entrada que é o arquivo que vamos ler e outro para saída, que será o arquivo que receberá o conteúdo já tratado.

Nosso último passo será descompactar e transcodificar o conteúdo do arquivo, de ISO-8859-1 para UTF-8. Agora é que vamos fazer jus ao título do post.

<?php  
    $entrada = fopen('teste.txt.bz2', 'r');
    $saida   = fopen('teste_fixed.txt', 'w');

    // Primeiro adicionamos um filtro de decodificação.
    stream_filter_prepend($entrada, 'bzip2.decompress', STREAM_FILTER_READ);

    // Trocamos o charset de  ISO-8859-1 para UTF-8.
    stream_filter_append($saida, 'convert.iconv.ISO-8859-1/UTF-8', STREAM_FILTER_WRITE);

    // Passamos todo o texto para maiúscula.
    stream_filter_append($saida, 'string.toupper', STREAM_FILTER_WRITE);

    // Agora copiamos para o arquivo de saída. Todos os filtros serão aplicados aqui.
    stream_copy_to_stream($entrada, $saida);

    // fechamos os handlers.
    fclose($entrada);
    fclose($saida);
?>

Basicamente, o que o código utilizando stream filter acima faz é:

  • Nós abrimos dois handlers, uma para ao arquivo de entrada e um para o arquivo de saída.
  • Atribuímos um stream_filter bzip2.decompress em modo de leitura, que vai descomprimir o fluxo de entrada como se lê.
  • Utilizamos um stream filter com iconv para transcodificar de ISO-8859-1 para UTF-8.
  • Utilizamos outro stream filter com string.toupper para transformar os dados para maiúsculas quando aplicável.
  • Depois copiar o fluxo de entrada ($entrada) para o fluxo de saída ($saida) num único passo.
  • Finalmente, fechamos os handlers..

É importante observar que nenhum dos filtros são realmente aplicados até que as stream são processadas. Por isso, somente quando streamcopyto_stream() é executado de que os quatro filtros são aplicados.

Este método é muito mais eficiente do que executar as mesmas operações em loop porque a cópia é feita a um nível inferior, em que os dados não tem de ser passado para dentro e para fora do espaço do utilizador. Assim, além de ser mais fácil de implementar, também é mais rápido e a utilização de memória é menor.