Streams em PHP: O Poder Oculto da Linguagem
O Básico: Lendo Arquivos
Vamos começar com algo familiar. Todo mundo conhece esse código básico de leitura de arquivos:
$file = fopen('names.txt', 'r');
while ($line = fgets($file)) {
echo $line;
}
fclose($file);
Simples, direto: abre um arquivo, lê linha por linha, fecha. Nada de novo aqui.
A Virada: Inception com Streams
Agora fica interessante. E se eu te disser que o fopen não trabalha apenas com arquivos locais? Ele trabalha com múltiplos protocolos.
Quando você escreve fopen('names.txt', 'r'), na verdade está fazendo fopen('file://names.txt', 'r'). O PHP abstrai toda a complexidade através de stream wrappers.
E o mais legal? Funciona com HTTP também! Você pode fazer requisições web usando as mesmas funções de arquivo:
$file = fopen('http://localhost:8001/names.php', 'r');
while ($line = fgets($file)) {
echo $line;
}
fclose($file);
O PHP abstrai toda a complexidade do protocolo HTTP (headers, requests, responses) através desses wrappers mágicos. Você está literalmente fazendo uma requisição HTTP, mas usando funções de arquivo!
Filtros: Transformando Dados em Tempo Real
Aqui é onde as coisas ficam realmente legais. O PHP tem filtros embutidos que você pode aplicar nas streams. Por exemplo:
Filter Strip Tags
Remove todas as tags HTML de uma stream. Perfeito para quando você está no terminal e não quer ver um monte de tags bagunçando sua saída:
$file = fopen('php://filter/read=string.strip_tags/resource=names.txt', 'r');
fpassthru($file);
Filter ROT13
Rotaciona 13 posições no código ASCII de cada caractere. É tipo uma criptografia bem básica (bem pobre mesmo). O texto fica parecendo russo, mas é só ROT13:
$file = fopen('php://filter/read=string.rot13/resource=names.txt', 'r');
fpassthru($file);
PHP como Pipe no Terminal
Aqui vem a mágica: você pode usar o PHP como pipe no terminal, tipo o grep!
php fopen.php | php -r "stream_copy_to_stream(
fopen('php://filter/read=string.strip_tags/resource=php://stdin', 'r'),
fopen('php://stdout', 'w')
);"
O melhor? A função stream_copy_to_stream() copia de uma stream para outra sem usar memória do PHP. É praticamente zero de memória consumida. Eficiência pura!
Você pode encadear quantos filtros quiser:
php fopen.php | php -r "stream_copy_to_stream(
fopen('php://filter/read=string.strip_tags|string.rot13/resource=php://stdin', 'r'),
fopen('php://stdout', 'w')
);"
Agora removeu as tags E transformou em ROT13. Tudo em uma linha!
Criando Filtros Customizados
Aqui começam as bizarrices do PHP. Para criar filtros customizados, você precisa estender a classe php_user_filter (sim, com esse nome bizarro que não tem nada a ver com as outras classes do PHP).
Isso vem do PHP 4, quando o modelo de objetos ainda não estava bem definido. A implementação usa um esquema de "buckets" copiado do Apache:
class UpperFilter extends php_user_filter {
public function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = strtoupper($bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register('upper', 'UpperFilter');
$file = fopen('php://filter/read=string.strip_tags|upper/resource=php://stdin', 'r');
stream_copy_to_stream($file, fopen('php://stdout', 'w'));
Dei toda essa volta só para transformar um texto em maiúsculas, mas é o jeito mais simples de mostrar essa API.
Contextos: Controlando Requisições HTTP
Você pode criar contextos para controlar como as requisições são feitas. O contexto é aquele quarto parâmetro esquisito do fopen:
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode(['data' => 'valor'])
]
]);
$result = file_get_contents('http://api.exemplo.com', false, $context);
Com isso você pode fazer requisições HTTP muito mais relevantes: mudar o método (POST, PUT, DELETE), passar headers customizados, enviar body... tudo isso!
Implementando o Comando yes em PHP
Alguém conhece o comando yes do terminal? Ele fica repetindo "y" infinitamente. É útil para automatizar comandos que pedem confirmação.
Vamos implementar nosso próprio yes criando um stream wrapper customizado:
class YesWrapper {
// implements streamWrapper (não existe de verdade, só na documentação)
public function stream_open($path, $mode, $options, &$opened_path) {
return true; // sempre abre com sucesso
}
public function stream_read($count) {
usleep(1000000); // 1 segundo de sleep
return "y\n"; // sempre retorna y
}
public function stream_eof() {
return false; // nunca termina, é infinito!
}
public function stream_stat() {
return []; // informações do arquivo
}
}
stream_wrapper_register('yes', 'YesWrapper');
fpassthru(fopen('yes://', 'r'));
Isso cria um arquivo virtual infinito que só tem "y"s! É como se você estivesse lendo de um arquivo que nunca acaba.
Mock de Arquivos com vfsStream (Respect/Test)
Aqui fica extremamente útil: imagine que você tem código que mexe com arquivos e quer testar sem criar arquivos reais. O Respect/Test tem uma feature incrível:
use org\bovigo\vfs\vfsStream;
vfsStream::setup();
vfsStream::create([
'pandas.txt' => "Fulano\nCiclano\nBeutrano"
]);
// Agora você pode usar vfs://pandas.txt como se fosse um arquivo real!
$content = file_get_contents('vfs://pandas.txt');
// Pode gravar também
file_put_contents('vfs://pandas.txt', 'novos dados');
Você cria um sistema de arquivos virtual completamente em memória. Perfeito para testes unitários! Não precisa criar arquivos temporários, fazer cleanup, nada disso.
Detalhe importante: use clearstatcache() se precisar trocar entre o sistema real e o virtual, porque o PHP cacheia informações de arquivos.
Socket Servers: Criando Seu Próprio Servidor
Você não precisa apenas consumir streams. Você pode criar streams também! Vamos criar um socket server:
$server = stream_socket_server('tcp://localhost:1234', $errno, $errstr);
while ($client = stream_socket_accept($server, -1)) {
$peer = stream_socket_get_name($client, true);
echo "Conectado: $peer\n";
while ($data = fgets($client)) {
echo "Recebido: $data";
}
fclose($client);
}
Agora você tem um servidor TCP rodando! Pode conectar nele usando nc localhost 1234 (netcat) ou telnet.
O problema? Esse código aceita apenas um cliente por vez. Se um segundo cliente tentar conectar, ele fica esperando o primeiro terminar.
Event Loop com stream_select()
Aqui vem a mágica para suportar múltiplos clientes simultâneos:
$server = stream_socket_server('tcp://0.0.0.0:1234', $errno, $errstr);
$clients = [];
while (true) {
$reads = array_merge([$server], $clients);
$writes = $excepts = null;
stream_select($reads, $writes, $excepts, 60);
if (in_array($server, $reads)) {
$client = stream_socket_accept($server, 0);
$peer = stream_socket_get_name($client, true);
echo "Conectado: $peer\n";
$clients[] = $client;
unset($reads[array_search($server, $reads)]);
}
foreach ($reads as $client) {
$data = fgets($client);
if (!$data) {
echo "Cliente desconectou\n";
fclose($client);
unset($clients[array_search($client, $clients)]);
} else {
echo "Recebido: $data";
}
}
}
O stream_select() é um event loop. Ele monitora múltiplas streams ao mesmo tempo e te avisa quando alguma tem dados para ler.
Agora você pode ter vários clientes conectados simultaneamente! Um não bloqueia o outro.
O Que É stream_select()?
O stream_select() é basicamente um bind para a função select() do sistema operacional (system call em C). É bem low-level.
Não é thread, não é multi-processo. É um tipo de paralelismo diferente: você processa eventos de múltiplas conexões em um único processo. É o mesmo conceito usado por frameworks assíncronos.
Quem Usa Isso?
Você pode estar pensando: "legal, mas quem usa isso na vida real?"
- Guzzle: biblioteca HTTP famosa, usa streams
- ReactPHP: framework assíncrono, usa event loops com streams
- Respect/Test: para mockar arquivos em testes
Streams são a base de muita coisa no PHP, só que ninguém fala sobre elas!
Três APIs Bizarras para a Mesma Coisa
O PHP tem três APIs diferentes que mexem com streams:
- Funções básicas:
fopen(),fgets(),fread(),fclose()- a API tradicional de arquivos - Filtros e Wrappers:
php_user_filter,stream_wrapper_register()- vem do PHP 4, antes do modelo de objetos estar definido - Sockets:
stream_socket_server(),stream_select()- bindings diretos para system calls do C
Cada uma tem suas peculiaridades e bizarrices, mas todas trabalham com o mesmo conceito de streams.
Conclusão
Streams em PHP são poderosas, bizarras e completamente subutilizadas. Você pode:
- Ler de HTTP como se fossem arquivos
- Criar filtros customizados para transformar dados
- Mockar arquivos para testes
- Criar servidores TCP do zero
- Fazer paralelismo com event loops
- Processar dados com consumo zero de memória
É low-level, é estranho, mas funciona. E abre um mundo de possibilidades que você provavelmente nunca imaginou que existiam no PHP.
Agora vai lá experimentar! E quando der erro (porque vai dar), lembre-se: quanto mais gente assistindo, maior a chance de dar errado. É a lei do live coding! 😄
Gostou do post?
Deixe seu like e ajude a divulgar o conteúdo!