A palavra stream significa corrente. Em geral, qualquer conexão de rede é uma stream, e existem vários tipos de protocolos para streams. Esses protocolos definem como os dados fluem na corrente.
No PHP, vários protocolos são suportados de forma transparente:
<?php
$arquivoDoFtp = file_get_contents('ftp://example.com/my/file.txt');
$arquivoDoDisco = file_get_contents('/my/file.txt');
$arquivoDoHttp = file_get_contents('http://example.com/my/file.txt');
No exemplo acima usamos a mesma função do PHP, a file_get_contents
para obter o conteúdo de três arquivos em lugares diferentes suportados por protocolos diferentes. O PHP decide qual protocolo de stream usar pelo identificador de esquema algo://
, se ele for omitido, cai para o padrão file://
.
O mais legal disso é que todas as funções de arquivos e streams do PHP funcionam com todos os protocolos. Vejamos um exemplo mais legal:
<?php
$pastaDoFtp = scandir('ftp://example.com/my/images/folder');
$pastaDoDisco = scandir('/my/images/folder');
$pastaDoGz = scandir('zlib:///my/images/folder.gz');
A função scandir
é uma das funções de arquivo, portanto ela é capaz de abstrair streams e varrer diretórios tanto locais quanto de um FTP remoto ou arquivo zipado.
Alguns protocolos suportam contextos. Eles são necessários pra trafegar mais informação sobre uma stream. O contexto pra HTTP por exemplo, entre os diversos recursos que ele suporta, permite definir cabeçalhos:
<?php
$contexto = stream_context_create(array('http' => array(
'user_agent' => 'My PHP Bot.'
)));
$arquivoDoHttp = file_get_contents(
'http://exemplo.com/my/file.txt',
null,
$contexto
);
O exemplo acima obtém um arquivo de um HTTP mas identifica a conexão com um cabeçalho User-Agent
. É possível enviar cabeçalhos de autenticação, cache, mudar o método para POST, DELETE ou qualquer outro e definir proxies.
Além disso, o PHP permite que você crie seus próprios protocolos e os registre. Pessoas já criaram vários como esses pra mexer com arquivos no Amazon S3 e repositórios Git com funções simples como file_get_contents
e scandir
.
Outra coisa bastante surpreendente no suporte a streams é a auto-cópia. No exemplo abaixo, o $arquivoHttp
é baixado inteiro para a memória antes de ser salvo:
<?php
$arquivoHttp = file_get_contents('http://example.com/my/file.txt');
file_put_contents('/arquivos/salvos/file.txt', $arquivoHttp);
Contudo, no exemplo abaixo, o arquivo nunca vai inteiro para a memória, permitindo que a stream flua livremente do HTTP direto pro disco:
<?php
file_put_contents(
'/arquivos/salvos/file.txt',
file_get_contents('http://example.com/my/file.txt')
);
Isso acontece porque se você passa uma função de stream como parâmetro de outra, o PHP automaticamente aplica a função stream_copy_to_stream
. Você poderia fazer isso manualmente também:
<?php
stream_copy_to_stream(
fopen('http://example.com/my/file.txt', 'r'),
fopen('/arquivos/salvos/file.txt', 'w+')
);
Isso permite que você mexa com arquivos de 2GB tendo apenas 28MB de limite de memória no PHP, porque ele nunca terá o arquivo inteiro na memória, só um pequeno buffer que ele usa pra passar a corrente entre duas streams!
E se você quiser, é possível ir ainda mais longe com os filtros de stream. No exemplo acima nós copiamos um arquivo do HTTP direto pro disco, mas não é possível ver seu conteúdo. Com filtros de stream isso é possível. O exemplo abaixo baixa um arquivo de um FTP, converte tudo para maiúsculas e salva em um disco local:
<?php
$arquivoFtpStream = fopen('ftp://example.com/file.txt', 'r');
$arquivoLocalStream = fopen('/my/file.txt', 'w+');
stream_filter_append($arquivoLocalStream, "string.toupper", STREAM_FILTER_WRITE);
stream_copy_to_stream($arquivoFtpStream, $arquivoLocalStream);
Usei o filtro string.toupper
, que é um dos padrões, mas você pode registrar os seus próprios.
Além de tudo, existem algumas streams específicas do PHP. O exemplo abaixo lê uma imagem gigante do disco e exibe ela no navegador sem carregá-la inteira na memória:
<?php
header('Content-Type: image/png');
fpassthru(fopen('/meu/arquivo/de/imagem/gigante.png', 'r'));
Além do fpassthru
, é possível usar uma stream explícita:
<?php
header('Content-Type: image/png');
stream_copy_to_stream(
fopen('/meu/arquivo/de/imagem/gigante.png', 'r'),
fopen('php://output', 'w+')
);
Além do php://output
também existem outras streams mágicas do php.
E finalmente, streams são a única forma de fazer o PHP trabalhar de forma assíncrona! Isso é feito com a função stream_select
, que se for usada em conexões não-bloqueantes, permite que as streams sejam coletadas de forma assíncrona.
Muito bom!