Sepanjang section ini kita membuka, menulis, dan mengunci berkas — dan di balik semuanya bekerja satu abstraksi yang sama: stream. Sebuah stream adalah sumber atau tujuan data yang dapat dibaca dan ditulis secara berurutan, dan PHP menyatukan berbagai sumber tersebut — berkas di disk, memori, input/output proses, bahkan koneksi jaringan — di balik satu set fungsi yang seragam. Inilah sebabnya fread(), fwrite(), dan fclose() yang Anda pakai untuk berkas juga bekerja untuk target-target lain. Pada Chapter ini kita bedah konsep stream, keluarga wrapper php://, operasi menyalin antar-stream, metadata, dan filter, sebelum ditutup dengan jembatan menuju stream contexts.

Apa Itu Stream #

Setiap kali fopen() berhasil, yang dikembalikan bukanlah isi berkas melainkan sebuah handle bertipe resource — pintu menuju stream yang terbuka. Seluruh fungsi operasi I/O PHP (fread(), fwrite(), feof(), rewind(), fclose()) bekerja pada handle ini tanpa peduli sumber data di baliknya. Abstraksi inilah yang membuat kode I/O PHP dapat ditulis sekali lalu dipakai untuk banyak jenis sumber.

php
<?php
$berkas = tempnam(sys_get_temp_dir(), "demo-");
$stream = fopen($berkas, "r");

var_dump(is_resource($stream));

fclose($stream);
unlink($berkas);

Output yang Dihasilkan:

text
bool(true)

Fungsi is_resource() mengonfirmasi bahwa fopen() benar-benar mengembalikan resource — handle yang akan kita operasikan pada seluruh contoh berikutnya. Apabila berkas tidak bisa dibuka, fopen() mengembalikan false sekaligus memicu warning, sehingga kode produksi selalu memeriksa hasilnya lebih dahulu.

Stream Wrapper php:// #

Sumber data di balik sebuah stream ditentukan oleh stream wrapper — skema yang tertulis di awal target yang Anda berikan ke fopen(). Selama section ini kita memakai wrapper implisit untuk berkas biasa; PHP juga membawa wrapper bawaan php:// untuk sumber-sumber khusus:

WrapperPeran
php://memoryStream baca-tulis yang seluruh isinya berada di memori
php://tempBerawal di memori, pindah ke berkas temporer bila melebihi batas
php://stdin, php://stdout, php://stderrInput dan output standar proses (utama di CLI)
php://filterMenyaring stream lain saat dibaca atau ditulis
php://inputIsi mentah body request (web SAPI)

Mari kita mulai dari php://memory — “berkas” yang hidup sepenuhnya di memori, tanpa satu byte pun menyentuh disk:

php
<?php
$stream = fopen("php://memory", "w+");

fwrite($stream, "Belajar streams di PHP\n");
rewind($stream);

echo "Isi stream: " . stream_get_contents($stream);
fclose($stream);

Output yang Dihasilkan:

text
Isi stream: Belajar streams di PHP

Mode "w+" membuka stream untuk baca dan tulis sekaligus. Setelah fwrite(), posisi baca-tulis berada di akhir data, sehingga rewind() memutarnya kembali ke awal sebelum stream_get_contents() membaca seluruh isi. Pola tulis-rewind-baca ini sama persis dengan pola pada berkas biasa — bukti pertama bahwa abstraksi stream benar-benar seragam.

php://temp dengan Batas Memori #

php://memory cepat tetapi menahan seluruh data di RAM, yang berbahaya untuk data besar. Wrapper php://temp/maxmemory:N menyediakan komprominya: data disimpan di memori selama ukurannya masih di bawah N byte, lalu secara transparan dipindahkan ke berkas temporer di disk begitu batas itu terlampaui — kode Anda tidak perlu berubah sama sekali.

php
<?php
$stream = fopen("php://temp/maxmemory:64", "w+");

fwrite($stream, "data kecil, muat di memori");
rewind($stream);
echo stream_get_contents($stream) . "\n";

$meta = stream_get_meta_data($stream);
echo "stream_type: " . $meta["stream_type"] . "\n";
echo "uri: " . $meta["uri"] . "\n";

fwrite($stream, str_repeat("A", 100));
rewind($stream);

$meta = stream_get_meta_data($stream);
echo "stream_type setelah meluap: " . $meta["stream_type"] . "\n";
echo "total terbaca: " . strlen(stream_get_contents($stream)) . " byte\n";

fclose($stream);

Output yang Dihasilkan:

text
data kecil, muat di memori
stream_type: TEMP
uri: php://temp/maxmemory:64
stream_type setelah meluap: TEMP
total terbaca: 126 byte

Tulis pertama (26 byte) masih berada di bawah batas 64 byte sehingga seluruh data dimuat di memori. Penulisan kedua menambah 100 byte sehingga total 126 byte melampaui batas — metadata menunjukkan stream_type tetap "TEMP" dan pembacaan ulang tetap menghasilkan 126 byte, karena perpindahan ke berkas temporer terjadi di balik layar tanpa mengubah cara stream dioperasikan. Berkas temporer di belakangnya pun akan dibersihkan otomatis saat stream ditutup, seperti perilaku tmpfile() pada Chapter Temporary Files .

Membaca dan Menyalin Stream #

Untuk membaca, fread($stream, $panjang) mengambil maksimal sejumlah byte tertentu dari posisi saat ini, sedangkan stream_get_contents($stream) mengambil seluruh sisa data mulai dari posisi saat ini. Sering kali kita perlu memindahkan data dari satu stream ke stream lain — menggabungkan laporan, menyalin unggahan, atau menyalurkan hasil unduhan. PHP menyediakan stream_copy_to_stream() yang melakukan perpindahan secara efisien tanpa loop PHP:

php
<?php
$sumber = fopen("php://memory", "w+");
fwrite($sumber, "Data yang akan disalin\n");
rewind($sumber);

$tujuan = fopen("php://memory", "w+");

$jumlah = stream_copy_to_stream($sumber, $tujuan);
rewind($tujuan);

echo "Tersalin $jumlah byte\n";
echo stream_get_contents($tujuan);

fclose($sumber);
fclose($tujuan);

Output yang Dihasilkan:

text
Tersalin 23 byte
Data yang akan disalin

Fungsi stream_copy_to_stream() mengembalikan jumlah byte yang berhasil dipindahkan — 23 byte pada contoh ini — lalu rewind() pada stream tujuan memposisikan ulang agar isinya bisa dibaca. Karena perpindahan terjadi di level implementasi PHP (bahkan memanfaatkan mekanisme memori-kernel bila tersedia), pendekatan ini jauh lebih hemat daripada loop fread()-fwrite() buatan sendiri, terutama pada data berukuran besar.

Metadata Stream dengan stream_get_meta_data() #

Setiap stream yang terbuka membawa informasi tentang dirinya: mode pembukaan, jenis wrapper, apakah stream dapat di-seek, hingga URI sumbernya. Fungsi stream_get_meta_data() mengambil seluruh informasi tersebut dalam bentuk array asosiatif. Contoh berikut membuka berkas nyata lewat wrapper berkas implisit, lalu memeriksa metadatanya:

php
<?php
$berkas = sys_get_temp_dir() . "/meta-demo.txt";
file_put_contents($berkas, "konten\n");
$stream = fopen($berkas, "r+");

$meta = stream_get_meta_data($stream);
echo "mode: " . $meta["mode"] . "\n";
echo "stream_type: " . $meta["stream_type"] . "\n";
echo "wrapper_type: " . $meta["wrapper_type"] . "\n";
echo "seekable: " . var_export($meta["seekable"], true) . "\n";
echo "uri sama dengan nama berkas: " . var_export($meta["uri"] === $berkas, true) . "\n";

fclose($stream);
unlink($berkas);

Output yang Dihasilkan:

text
mode: r+
stream_type: STDIO
wrapper_type: plainfile
seekable: true
uri sama dengan nama berkas: true

Kunci mode memuat mode pembukaan persis seperti argumen fopen(), wrapper_type "plainfile" menandakan wrapper berkas biasa, dan seekable menyatakan posisi stream dapat diputar dengan rewind()/fseek(). Kunci uri berisi sumber stream — pada wrapper berkas berupa path berkasnya. Contoh sengaja membandingkan uri dengan $berkas alih-alih mencetaknya, karena mencetak path absolut membuat output berbeda di setiap mesin. Pada stream php://memory, uri bernilai "php://memory" — itulah yang dicetak pada contoh php://temp di bagian sebelumnya.

Wrapper data:// dan php://filter #

Keluarga wrapper tidak berhenti di berkas dan memori. Wrapper data:// membaca data mentah yang tertanam langsung di URL — berguna untuk menguji kode pemrosesan tanpa berkas; wrapper compress.zlib:// membaca berkas terkompresi seolah berkas biasa; dan http:// membuka URL jarak jauh (bila directive allow_url_fopen aktif) sehingga fopen("https://...", "r") membaca respons HTTP sebagai stream.

Sementara itu, php://filter adalah wrapper istimewa yang membungkus stream lain untuk menyaring datanya saat dibaca atau ditulis. Parameternya dirangkai dalam pola php://filter/read=<filter>/resource=<sumber>:

php
<?php
$stream = fopen("data://text/plain,Halo dari wrapper", "r");
echo stream_get_contents($stream) . "\n";
fclose($stream);

$filter = fopen("php://filter/read=string.toupper/resource=data://text/plain,halo budi", "r");
echo stream_get_contents($filter) . "\n";
fclose($filter);

Output yang Dihasilkan:

text
Halo dari wrapper
HALO BUDI

Baris pertama menunjukkan data://text/plain, diikuti isi data — dibaca sebagai stream biasa. Baris kedua merangkai dua wrapper sekaligus: php://filter dengan filter string.toupper membungkus data:// sebagai sumbernya, sehingga data yang terbaca sudah berhuruf besar. Pola yang sama berlaku untuk berkas nyata — misalnya php://filter/read=convert.base64-encode/resource=gambar.jpg membaca berkas sambil meng-encode-nya ke Base64, dan filter write= bekerja pada arah sebaliknya saat menulis.

php://stdout dan php://stdin #

Pada eksekusi CLI, php://stdout dan php://stderr mengarah ke output standar proses, sedangkan php://stdin mengarah ke input standarnya. Ketiganya penting untuk skrip yang dirangkai dalam pipeline shell. Karena stream ini adalah stream biasa, keduanya bisa dioper ke fungsi-fungsi stream seperti target lain:

php
<?php
$stdout = fopen("php://stdout", "w");
fwrite($stdout, "Pesan lewat stream php://stdout\n");
fclose($stdout);

Output yang Dihasilkan:

text
Pesan lewat stream php://stdout

Pembacaan php://stdin bersifat interaktif — ia menunggu data masuk dari pengguna atau pipeline — sehingga contohnya tidak dapat diverifikasi otomatis dan perlu Anda coba sendiri:

php
<?php
// baca-nama.php — jalankan dengan: echo "Budi Santoso" | php baca-nama.php
$stdin = fopen("php://stdin", "r");

$nama = trim(fgets($stdin));
echo "Halo, $nama!\n";

Dengan perintah echo "Budi Santoso" | php baca-nama.php, teks dari sisi kiri pipe mengalir ke php://stdin, dibaca satu baris oleh fgets(), lalu skrip membalasnya dengan Halo, Budi Santoso!. Tanpa input dari pipe maupun terminal, pembacaan php://stdin akan menunggu tanpa batas — perilaku yang wajar untuk input interaktif namun perlu diingat saat menguji skrip.

Preview: Stream Context #

Satu kekuatan besar arsitektur stream belum kita sentuh: banyak wrapper menerima opsi tambahan — metode dan header untuk http://, mode kompresi untuk zip://, dan lainnya — melalui stream context. Sebuah context dibuat dengan stream_context_create() dari array opsi yang dikelompokkan per wrapper, lalu diteruskan sebagai argumen terakhir pada fopen() atau file_get_contents():

php
<?php
$opsi = [
    "http" => [
        "method" => "GET",
        "header" => "User-Agent: KursusPHP/1.0\r\n",
    ],
];
$context = stream_context_create($opsi);

var_dump(is_resource($context));

Output yang Dihasilkan:

text
bool(true)

Array $opsi di atas menyiapkan permintaan HTTP GET dengan header User-Agent khusus; kunci "http" menandakan opsi tersebut ditujukan bagi wrapper http://. Context hasilnya berupa resource yang siap dikaitkan ke operasi stream mana pun. Bagaimana context mengubah perilaku wrapper, bagaimana menangani timeout dan respons, dan bagaimana memakainya untuk request API — semuanya dibedah tuntas pada Chapter Stream Contexts.

Ringkasan #

Stream menyatukan seluruh I/O PHP di balik satu API: fopen() mengembalikan handle, fread()/fwrite()/fclose() mengoperasikannya, dan wrapper menentukan sumber datanya — berkas, php://memory, php://temp, input/output standar, hingga URL jarak jauh. stream_get_contents() dan stream_copy_to_stream() menyederhanakan pembacaan dan perpindahan data, stream_get_meta_data() membuka identitas sebuah stream, dan php://filter menyaring data di tengah jalan. Dengan konsep stream di tangan, perjalanan kita berlanjut ke lapisan kendali di atasnya: stream contexts, pengatur opsi yang menjadikan stream PHP sungguh-sungguh fleksibel.

Referensi #