Streams no PHP

Streams representam coleções de dados que podem não estar completamente disponíveis de imediato e que também não possuem a limitação de terem que caber de uma só vez na memória. Isso faz com que elas sejam uma poderosa ferramenta para lidarmos com os dados em partes (chunks) e acessados sob demanda. Uma definição mais técnica para streams é que representam a transmissão/fluxo de dados de uma fonte para um destino.

Entender o essencial sobre streams é fundamental para se trabalhar com arquivos. Sem streams não conseguiríamos abrir um arquivo de 20GB se não tivéssemos 20GB de memória disponíveis. Com elas, abrimos o arquivo e o lemos em partes. Sistemas operacionais baseados no Unix proveem uma interface padrão para operações I/O (input e output) que se passa por descritores de arquivo. Um descritor de arquivo é representado por um número inteiro que se associa a um arquivo aberto e, nesses sistemas, há uma generalização de que “tudo é um arquivo”, então, quando falamos aqui de arquivo, ele pode ser uma conexão de rede (um socket é um tipo especial de arquivo), um arquivo de texto, um arquivo de vídeo ou até mesmo uma pasta (que é um tipo especial de arquivo).

PHP Básico
Curso de PHP Básico
CONHEÇA O CURSO

Grande parte do que é feito no PHP é sobre streams. Ao ler um arquivo, lidamos com streams. Ao retornar um output para o cliente, lidamos com streams. Ao ler um input, lidamos com streams. Ao obter dados de uma conexão TCP/IP, também estaremos trabalhando com streams. O PHP abstrai ao máximo o trabalho com as streams e, na maior parte do tempo, usamos funções de alto nível da linguagem que deixam bem transparente o uso delas. No entanto, há momentos em que precisamos conhecer um pouco mais sobre elas para que consigamos resolver problemas e necessidades específicas. Essa é a ideia desse artigo.

Operações básicas com streams

Uma stream é referenciada dessa maneira:

<scheme>://<target>

Scheme é nome do wrapper que está sendo utilizado. Target é a identificação do recurso que será trabalhado. E esse recurso vai depender do contexto, se estamos falando do sistema de arquivos ou da rede, por exemplo.

O PHP possui algumas funções genéricas que nos permitem interagir com streams e até mesmo mudar o comportamento delas. Muitas das funções que trabalham com arquivos permitem essa interação. Algumas das mais conhecidas são:

  • fopen()
  • fwrite() ou fputs()
  • fclose()
  • file_get_contents()
  • file_put_contents()
  • Entre outras.

O PHP possui uma extensão chamada Stream que já vem habilitada por padrão. Como o próprio nome sugere, ela lida com toda a abstração de streams de I/O (input e output) do PHP. No decorrer do artigo utilizaremos algumas dessas funções.

Antes, apenas para dar contexto ao que falamos anteriormente de que com streams lidamos com chunks e que isso permite trabalharmos com grandes quantidade de dados sem estourar o limite de memória, desenvolveremos um simples exemplo.

Se você usa um sistema baseado em unix, em um diretório de testes, crie o seguinte arquivo:

$ awk 'BEGIN { n = 1; while (n < 10000000) print (n++) }' > numeros.txt

O arquivo criado tem ~78MB e é composto apenas de números sequenciais. Agora, digamos hipoteticamente que a nossa instalação do PHP nos permite usar apenas 4MB por script, vamos tentar ler o arquivo utilizando file_get_contents():

<?php

ini_set('memory_limit', '4M');

$file = file_get_contents('numeros.txt');

Ao executar, teremos o erro:

PHP Fatal error:  Allowed memory size of ...

Ele tentou alocar mais memória que o permitido, pois o arquivo é muito grande e a file_get_contents() lê o arquivo inteiro na memória.

Agora, a coisa muda quando passamos a utilizar fopen() e fgets(). A fopen() lida “diretamente” com descritores de arquivos, ela apenas abre uma stream para que possamos utilizá-la. Ou seja, dar um echo em fopen não produzirá nenhum resultado, diferentemente de file_get_contents(). Se você der um var_dump() na fopen() verá que é um resource do tipo stream.

<?php

$file = fopen('numeros.txt', 'rb');

var_dump($file); // resource(5) of type (stream)

Usando fgets() podemos ler a linha da stream aberta, sem carregar o arquivo inteiro na memória:

<?php

ini_set('memory_limit', '4M');

$stream = fopen('numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

echo 'Memória utilizada: ' . (memory_get_peak_usage(true) / 1024 / 1024);

fclose($stream);

A função feof() avalia se já chegou ao final do arquivo, se não, continua no while até que o arquivo seja completamente lido e seus dados impressos no output buffer. A fgets() lê linha a linha da stream aberta.

No entanto, a fgets() retorna false quando não tiver mais nenhum byte a ser lido e, nesse sentido, o while do exemplo acima poderia ser reescrito para:

while (($line = fgets($stream)) !== false) {
    echo $line;
}

Você pode usar a abordagem que preferir, ambas resolvem o problema.

O principal wrapper de streams é o file://, ele é o padrão utilizado pela fopen() e outras funções relacionadas. Sempre que acessamos o sistema de arquivos, estamos de forma transparente o utilizando. Por esse motivo, o mesmo exemplo acima poderia ser reescrito para:

<?php

$stream = fopen('file://' . __DIR__ . '/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Se abríssemos o servidor embutido do PHP no diretório onde o exemplo está salvo:

$ php -S localhost:8000

Também conseguiríamos acessar o numeros.txt através do wrapper http:// do PHP:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Teríamos o mesmo resultado, mas no fundo seria aberto um socket TCP/IP para acessar esse servidor, ou seja, no caso desse exemplo em particular em que só precisamos abrir o arquivo e ele está localmente disponível no nosso sistema de arquivo, abrí-lo através da rede é desnecessário. Fizemos isso apenas para fins didáticos.

Falando em sockets, uma dica de leitura: Programação de Sockets em PHP

Nos exemplos acima usamos fgets() que lê linha a linha, mas se temos a necessidade de pegar chunks de um determinado tamanho em bytes por vez, podemos usar a fread():

<?php

ini_set('memory_limit', '4M');

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

echo fread($stream, filesize('numeros.txt'));

Nesse exemplo estamos pedindo pra fread() ler o tamanho total do arquivo, o que não será possível, devido ao limite de memória de 4MB que temos. Vai estourar o erro da incapacidade de alocação desses dados.

Agora, se a gente diminui o tanto de bytes que queremos ler por vez:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

while (feof($stream) === false) {
    echo '[-------- CHUNK --------]' . PHP_EOL;
    echo fread($stream, 2048);
}

A cada novo [-------- CHUNK --------] que você notar no terminal significa que 2048 bytes foram lidos.

A função stream_get_meta_data() retorna os metadados de uma stream aberta:

<?php

$stream = fopen('numeros.txt', 'rb');

var_dump(stream_get_meta_data($stream));

fclose($stream);

Teria o retorno:

array(9) {
  'timed_out' => bool(false)
  'blocked' => bool(true)
  'eof' => bool(false)
  'wrapper_type' => string(9) "plainfile"
  'stream_type' => string(5) "STDIO"
  'mode' => string(2) "rb"
  'unread_bytes' => int(0)
  'seekable' => bool(true)
  'uri' => string(11) "numeros.txt"
}

Numa stream que usa o wrapper http teríamos algumas informações adicionais como alguns cabeçalhos http da resposta:

<?php

$stream = fopen('http://localhost:8000/numeros.txt', 'rb');

var_dump(stream_get_meta_data($stream));

fclose($stream);

E o retorno é:

array(10) {
  'timed_out' => bool(false)
  'blocked' => bool(true)
  'eof' => bool(false)
  'wrapper_data' => array(6) {
    [0] => string(15) "HTTP/1.0 200 OK"
    [1] => string(20) "Host: localhost:8000"
    [2] => string(35) "Date: Wed, 21 Aug 2019 18:41:52 GMT"
    [3] => string(17) "Connection: close"
    [4] => string(39) "Content-Type: text/plain; charset=UTF-8"
    [5] => string(24) "Content-Length: 78888888"
  }
  'wrapper_type' => string(4) "http"
  'stream_type' => string(14) "tcp_socket/ssl"
  'mode' => string(2) "rb"
  'unread_bytes' => int(0)
  'seekable' => bool(false)
  'uri' => string(33) "http://localhost:8000/numeros.txt"
}

Voltaremos em breve no wrapper http quando vermos sobre contextos de streams.

A função stream_copy_to_stream()

A função stream_copy_to_stream() permite copiar dados de uma stream para outra. Isso costuma ser útil em algumas operações on-the-fly.

O exemplo mais básico de como isso pode ser feito:

<?php

$destination = fopen('cep.json', 'wb');
$source = fopen('https://viacep.com.br/ws/01001000/json/', 'rb');

stream_copy_to_stream($source, $destination);

Copiará para o arquivo cep.json os dados desse CEP.

PHP Intermediário
Curso de PHP Intermediário
CONHEÇA O CURSO

O wrapper php://

Esse é um wrapper especial que permite com que acessemos as streams de I/O da linguagem. Por exemplo, se queremos ler a STDIN (standard input stream) do PHP e depois escrever direto na STDOUT (standard output stream):

<?php

$input = fopen('php://stdin', 'rb');
$output = fopen('php://stdout', 'rb');

fwrite($output, 'Ei, fale algo. :D' . PHP_EOL);

while (true) {
    $line = trim(fgets($input));

    if ($line === ':close') {
        fwrite($output, 'Bye!');

        break;
    }

    fwrite($output, "User says: {$line}" . PHP_EOL);
}

fclose($input);
fclose($output);

Esse exemplo abre duas streams de I/O do PHP, uma relacionada ao input e outra ao output. Depois, num laço infinito ficamos “ouvindo” por novas entradas e escrevemos de volta tudo o que recebemos.

Se você testar o exemplo executando no seu terminal:

$ php index.php
Ei, fale algo. :D
Oi!
User says: Oi!
PHP
User says: PHP
:close
Bye!

Definimos :close como sendo o comando que vai interromper o while.

Ao invés de explicitamente abrimos a stdin e stdout usando fopen(), poderíamos usar duas constantes que o PHP disponibiliza que já fazem esse trabalho pra nós, são as STDIN e STDOUT.

Internamente o PHP define essas constantes:

define('STDIN', fopen('php://stdin', 'r'));
define('STDOUT', fopen('php://stdout', 'w'));

Por exemplo:

<?php

echo fgets(STDIN);

No terminal, execute:

$ echo "Hello World" | php index.php

O resultado será Hello World. O | refere-se ao mecanismo de pipiline do unix, onde podemos passar o stdout de um processo para o stdin de outro (uma forma rudimentar de se atingir inter-process communication).

Poderíamos alterar o exemplo para utilizar essas constantes e torná-lo menos verboso:

<?php

fwrite(STDOUT, 'Ei, fale algo. :D' . PHP_EOL);

while (true) {
    $line = trim(fgets(STDIN));

    if ($line === ':close') {
        fwrite(STDOUT, 'Bye!');

        break;
    }

    fwrite(STDOUT, "User says: {$line}" . PHP_EOL);
}

A relação com todos os I/O streams disponíveis no PHP você pode acessar clicando aqui.

Também poderíamos escrever direto na stream de output do PHP, no sentido de mandar pro output buffer dados sem que precisemos usar echo ou print. Por exemplo:

<?php

$ouput = fopen('php://output', 'rb');

fwrite($ouput, 'Hello World');

fclose($ouput);

Isso é o mesmo que:

<?php

echo 'Hellow World!';

O PHP implementa outros wrappers nativamente, a lista dos disponíveis pode ser obtida executando:

$ php -r "var_dump(stream_get_wrappers());"
array(12) {
  [0] => string(5) "https"
  [1] => string(4) "ftps"
  [2] => string(13) "compress.zlib"
  [3] => string(14) "compress.bzip2"
  [4] => string(3) "php"
  [5] => string(4) "file"
  [6] => string(4) "glob"
  [7] => string(4) "data"
  [8] => string(4) "http"
  [9] => string(3) "ftp"
  [10] => string(4) "phar"
  [11] => string(3) "zip"
}

As streams php://temp e php://memory

São streams de leitura e escrita que permitem o armazenamento temporário de dados na memória, com a diferença que a php://temp armazena na memória até que se atinja o limite padrão de 2MB (esse limite pode ser alterado por configuração), depois disso, os dados são armazenados em arquivo temporário no sistema de arquivos.

Elas são úteis quando se tem a necessidade de tratar um determinado dado como um stream, por exemplo:

<?php

function createStreamFromString(string $contents) {
    $stream = fopen('php://memory', 'r+b');

    fwrite($stream, $contents);
    rewind($stream);

    return $stream;
}

echo fgets(createStreamFromString('Hello World!'));

A função rewind() volta o ponteiro do arquivo para o início.

Um possível caso de uso pra essa abordagem seria, por exemplo, em memória a construção de um arquivo CSV, depois, a compactação dele (para ZIP) e o streaming disso para quem requisitar, sem envolver salvar no disco, por exemplo. No entanto, esse mesmo objetivo pode ser atingido mais facilmente usando a library ZipStream-PHP que abstrai numa API de alto nível o streaming de arquivos zips:

setSendHttpHeaders(true);

$zip = new ZipStream\ZipStream('example.zip', $options);
$zip-&gt;addFile('hello.txt', 'This is the contents of hello.txt');

$zip-&gt;finish();
PHP Avançado
Curso de PHP Avançado
CONHEÇA O CURSO

A função stream_get_contents()

Identica à file_get_contents(), com a diferença que essa função retorna os dados de uma stream aberta. O exemplo anterior poderia ser reescrito para:

<?php

function createStreamFromString(string $contents) {
    $stream = fopen('php://memory', 'r+b');

    fwrite($stream, $contents);
    rewind($stream);

    return $stream;
}

echo stream_get_contents(createStreamFromString('Hello World!'));

Stream contexts

Um contexto é uma série de parâmetros e opções específicas de um determinado wrapper que pode ser informado na maioria das funções que trabalham com streams. Ele serve pra aprimorar o uso ou até mesmo alterar o comportamento de uma stream. Por exemplo, as funções file_get_contents() e file_put_contents() aceitam contexto de stream como parâmetro. Um contexto é criado usando a função stream_context_create().

O uso mais comum de contextos para streams se dá na definição de cabeçalhos HTTP usando o wrapper http. Por exemplo, crie um arquivo producer.php:

<?php

header('Content-Type: application/json');

$id = $_POST['id'];
$qtd = $_POST['qtd'];

if ( ! isset($_POST['id'], $_POST['qtd'])) {
    http_response_code(400);

    exit;
}

echo json_encode([
    'id' => $id,
    'qtd' => $qtd,
    'timestamp' => time(),
]);

E em outro arquivo, no meu caso estou usando um index.php, podemos requisitá-lo passando os dados que ele exige:

<?php

$context = stream_context_create([
    'http'=> [
        'method' => 'POST',
        'header'=> 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query([
            'id' => 1,
            'qtd' => 10,
        ]),
    ]
]);

$json = file_get_contents('http://localhost:8000/producer.php', false, $context);

var_dump(json_decode($json, true));

Lembrando que o servidor embutido do PHP precisa estar rodando no diretório do exemplo:

$ php -S localhost:8000

E então, podemos executar:

$ php index.php
array(3) {
  'id' => string(1) "1"
  'qtd' => string(2) "10"
  'timestamp' => int(1566422525)
}

Todas as opções do contexto do wrapper http podem ser vistas em Opções de contexto do HTTP. E o índice com as opções para os diferentes wrappers pode ser visto em Opções e parâmetros de contexto.

O mesmo exemplo consumidor poderia ser reescrito para:

<?php

$context = stream_context_create([
    'http'=> [
        'method' => 'POST',
        'header'=> 'Content-type: application/x-www-form-urlencoded',
        'content' => http_build_query([
            'id' => 1,
            'qtd' => 10,
        ]),
    ]
]);

$stream = fopen('http://localhost:8000/producer.php', 'rb', false, $context);

$json = '';

while (feof($stream) === false) {
    $json .= fgets($stream);
}

var_dump(json_decode($json, true));

fclose($stream);

Ficou mais verboso, mas atingimos o mesmo resultado. Essa segunda opção utilizando fopen() é puramente didática, para mostrar que contextos podem ser trabalhados com fopen() normalmente, mas ela não é a melhor opção para esse exemplo em específico.

Uma função interessante e que pode ser útil dependendo do que se vá desenvolver é a fpassthru(), ela recebe um resource de uma stream e imprime compulsivamente tudo o que recebe dele. É como se ela fizesse um while (true) com echo fgets().

Vamos testá-la? Altere o exemplo de producer.php para:

<?php

$stream = fopen('numeros.txt', 'rb');

while (feof($stream) === false) {
    echo fgets($stream);
}

fclose($stream);

Estamos iterando e imprimindo as partes do arquivo. Agora, no index.php, vamos consumir isso:

<?php

$stream = fopen('http://localhost:8000/producer.php', 'rb');

fpassthru($stream);

fclose($stream);

Ao executar:

$ php index.php

Você verá um tsunami de números sendo impressos no seu terminal.

E, claro, nada impede que o producer.php também use fpassthru():

<?php

$stream = fopen('numeros.txt', 'rb');

fpassthru($stream);

fclose($stream);

Teremos o mesmo resultado.

Stream Filters

Um filtro performa uma transformação on-the-fly no dado que está sendo lido ou gravado em uma stream e N filtros podem ser usados ao mesmo tempo. Os filtros nativamente disponíveis podem ser listados executando:

$ php -r "var_dump(stream_get_filters());"
array(10) {
  [0] => string(6) "zlib.*"
  [1] => string(7) "bzip2.*"
  [2] => string(15) "convert.iconv.*"
  [3] => string(12) "string.rot13"
  [4] => string(14) "string.toupper"
  [5] => string(14) "string.tolower"
  [6] => string(17) "string.strip_tags"
  [7] => string(9) "convert.*"
  [8] => string(8) "consumed"
  [9] => string(7) "dechunk"
}

E, claro, podemos criar nossos próprios filtros. Mas, primeiro vamos focar em usar um filtro nativo Começaremos pelo filtro string.toupper que converte todos os dados da stream para caixa alta. Primeiro, crie um arquivo nomes.txt com o seguinte conteúdo:

<b>Thalia Bishop</b>
<b>Mckenna Carrillo</b>
<b>Wesley Curtis</b>
<b>Irene Neal</b>
<b>Norah Fuentes</b>
<b>Bailey Freeman</b>
<b>Lyric Shields</b>
<b>Chad Freeman</b>
<b>Beatrice Kline</b>
<b>Shayna Jennings</b>
<b>Clayton Potter</b>
<b>Karson Cowan</b>

E então podemos desenvolver esse exemplo:

<?php

$stream = fopen('nomes.txt', 'rb');

stream_filter_append($stream, 'string.toupper');

fpassthru($stream);

fclose($stream);

O retorno será todos os nomes em maiúsculo:

<B>THALIA BISHOP</B>
<B>MCKENNA CARRILLO</B>
<B>WESLEY CURTIS</B>
<B>IRENE NEAL</B>
<B>NORAH FUENTES</B>
<B>BAILEY FREEMAN</B>
<B>LYRIC SHIELDS</B>
<B>CHAD FREEMAN</B>
<B>BEATRICE KLINE</B>
<B>SHAYNA JENNINGS</B>
<B>CLAYTON POTTER</B>
<B>KARSON COWAN</B>

Tudo o que precisamos fazer é usar a função stream_filter_append() que adiciona um filtro na “stack” de filtros a serem executados naquela stream.

O filtro nativo string.strip_tags que aplica a função striptags() foi marcado como deprecated no PHP 7.3. Por esse motivo, vamos recriá-lo a partir de um filtro personalizado:

<?php

class StripTagsFilter extends \PHP_User_Filter
{
    public function filter($in, $out, &$consumed, $closing) : int
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = strip_tags($bucket->data);

            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}

// Registra o filtro
stream_filter_register('striptags', StripTagsFilter::class);

$stream = fopen('nomes.txt', 'rb');

stream_filter_append($stream, 'striptags');
stream_filter_append($stream, 'string.toupper');

fpassthru($stream);

fclose($stream);

O mais importante a ser notado é que a classe que criamos precisa estender \PHP_User_Filter. Os filtros trabalham com os dados no estilo de uma bucket brigade, onde cada bucket recuperado é passado de uma brigade para a outra. A função stream_bucket_make_writeable() faz algo parecido com o que a fgets() faz, só que retorna um objeto bucket que na realidade não possui um tipo específico, é um objeto genérico da stdclass. É no atributo data do bucket onde ficam os dados, é nele que fazemos as transformações que queremos. Na prática, estamos transformando os dados dos buckets da brigade $in e depois repassando esses buckets para a brigade $out. A constante PSFS_PASS_ON informa que terminamos de enviar os buckets para a brigade $out.

Você deve ter percebido que é bem confuso. O sistema de streams do PHP é algo muito poderoso e incrível. O problema está na API, em especial na de filtros, que é uma das mais confusas do PHP e não existe boa documentação para ela. Muitas vezes precisamos trabalhar com a intuição do que está acontecendo por debaixo dos panos. Por exemplo, o fato de ter que usar a função stream_bucket_make_writeable() dentro de um loop é uma recomendação da documentação do PHP, pois subentende-se que vários buckets possam ser recuperados de uma brigade, mas até hoje, em todos os exemplos que pude testar, não percebi tal necessidade.

Desenvolvedor PHP Júnior
Formação: Desenvolvedor PHP Júnior
Nesta formação você aprenderá todos os fundamentos necessário para iniciar do modo correto com a linguagem PHP, uma das mais utilizadas no mercado. Além dos conceitos de base, você também conhecerá as características e a sintaxe da linguagem de forma prática.
CONHEÇA A FORMAÇÃO

Uma abordagem moderna para se trabalhar com filtros de streams

Inspirado pela php-stream-filter, criei uma library chamada simple-stream-filter que abstrai a criação de filtros de streams, fazendo que com foquemos apenas na transformação do dado.

Vamos utilizá-la? No raiz do projeto, execute:

$ composer require kennedytedesco/simple-stream-filter

O mesmo exemplo anterior do filtro personalizado que criamos pode ser reescrito para:

<?php

require __DIR__.'/vendor/autoload.php';

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
});

fpassthru($stream);

fclose($stream);

Mais de um filtro pode ser aplicado:

<?php

require __DIR__.'/vendor/autoload.php';

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
});

Filter::append($stream, static function($chunk = null) {
    return strtoupper($chunk);
});

fpassthru($stream);

fclose($stream);

Se o desejo é aplicar o filtro apenas na leitura:

Filter::append($stream, static function($chunk = null) {
    return strip_tags($chunk);
}, STREAM_FILTER_READ);

Passe no terceiro parâmetro a constante STREAM_FILTER_READ (algo que será explicado mais abaixo).

Ou, como estamos usando funções nativas do PHP, também podemos simplesmente escrever assim:

<?php

require __DIR__.'/vendor/autoload.php';

use KennedyTedesco\SimpleStreamFilter\Filter;

$stream = fopen('nomes.txt', 'rb');

Filter::append($stream, 'strip_tags');

Filter::append($stream, 'strtoupper');

fpassthru($stream);

fclose($stream);

Dessa forma, abstraímos toda a dificuldade e verbosidade de se trabalhar com filtros básicos de streams.

Filtros na escrita de streams

Filtros também podem ser utilizados na escrita de streams. Crie um arquivo teste.txt vazio, no mesmo diretório dos exemplos. E então teste:

<?php

$stream = fopen('teste.txt', 'r+b');

stream_filter_append($stream, 'string.toupper');

fwrite($stream, 'hello world!');

Ao abrir o arquivo, você verá:

HELLO WORLD!

Mas, se a intenção é registrar um filtro apenas na escrita de uma stream, a stream_filter_append() possui um terceiro parâmetro que aceita as constantes:

  • STREAM_FILTER_WRITE
  • STREAM_FILTER_READ
  • STREAM_FILTER_ALL

A primeira marcará que o filtro só será aplicado na escrita. A segunda indicará que apenas na leitura. Se utilizarmos a constante STREAM_FILTER_READ :

<?php

$stream = fopen('teste.txt', 'r+b');

stream_filter_append($stream, 'string.toupper', STREAM_FILTER_READ);

fwrite($stream, 'hello world!');

Será escrito no arquivo:

hello world!

Não aplicou o string.toupper, ademais, exigimos que ele só fosse aplicado na leitura da stream. Se a ideia é permitir o filtro tanto pra escrita quanto pra leitura, o terceiro parâmetro pode ser ignorado ou pode se usar a terceira constante STREAM_FILTER_ALL que é o mesmo que declarar STREAM_FILTER_WRITE | STREAM_FILTER_READ, ou seja, libera tanto para escrita quanto pra leitura.

O meta-wrapper php://filter

Agora já vimos como usar e criar filtros, veremos sobre o meta-wrapper php://filter, apesar de não ser muito comum o seu uso, ele permite a aplicação de filtros junto com a abertura da stream. Útil, por exemplo, pra usar com funções como a file_get_contents() onde o registro tradicional de filtros não é aplicável, pois ela não retorna uma stream.

A sintaxe desse meta-wrapper é:

php://filter/string.toupper/resource=http://localhost:8000/nomes.txt

Informamos depois de filter/ os nomes dos filtros que serão aplicados nessa stream. Exemplo:

<?php

class StripTagsFilter extends \PHP_User_Filter
{
    public function filter($in, $out, &$consumed, $closing) : int
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $bucket->data = strip_tags($bucket->data);

            stream_bucket_append($out, $bucket);
        }

        return PSFS_PASS_ON;
    }
}

stream_filter_register('striptags', StripTagsFilter::class);

echo file_get_contents('php://filter/string.toupper|striptags/resource=nomes.txt');

Recuperamos os nomes já aplicando os filtros string.toupper e striptags (aquele registrado por nós). Da mesma forma, esse meta-wrapper pode ser usado para escrever em um arquivo, por exemplo:

<?php

file_put_contents(
    'php://filter/string.toupper/resource=teste-2.txt', 
    'hello world!'
);

O que ler a seguir?

Concluindo

Entender o essencial sobre streams pode ser uma poderosa ferramenta para resolver algumas das complexidades e dificuldades de se trabalhar com arquivos e sockets.

Recomendo também a leitura do artigo sequência deste: Stream Wrappers personalizados no PHP.

Até a próxima!

Deixe seu comentário

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP). @KennedyTedesco