Fungsi yang menerima “sekumpulan data” sering kali tidak perlu peduli wujud kumpulan itu: pemanggil bisa membawa array biasa, object ArrayIterator, atau generator yang baru dibuat. Sebelum PHP 7.1 tidak ada satu tipe yang menggambarkan ketiganya, sehingga API harus memilih satu wujud dan memaksa seluruh pemanggil menyesuaikan diri. Tipe iterable menutup celah tersebut. Pada chapter ini kita pelajari cara memakai iterable pada parameter dan nilai kembalian, memeriksanya dengan is_iterable(), serta aturan variansinya terhadap array dan Generator.

Tipe iterable: Array atau Traversable #

iterable adalah pseudo-type: nama ringkas untuk union array|Traversable. Interface Traversable adalah leluhur bersama seluruh iterator — Iterator dan IteratorAggregate sama-sama mewarisinya — sehingga satu nama iterable mencakup hampir semua wujud kumpulan data di PHP. Dua deklarasi parameter berikut menerima himpunan nilai yang persis sama:

php
<?php
// Kedua deklarasi ini menerima nilai yang sama:
// array, ArrayIterator, generator, dan iterator custom apa pun.
function versiPendek(iterable $data): void
{
}

function versiPanjang(array|Traversable $data): void
{
}

Bentuk iterable lebih ringkas dan langsung menyampaikan maksud, jadi gunakan bentuk tersebut kecuali Anda punya alasan menulis union secara eksplisit. Sama seperti tipe lainnya, iterable berlaku untuk parameter, nilai kembalian, dan pemeriksaan runtime.

Menerima iterable pada Parameter #

Parameter bertipe iterable siap menerima array dari pemanggil pertama, object ArrayIterator dari pemanggil kedua, dan generator dari pemanggil ketiga — tanpa cabang if apa pun di dalam fungsi:

php
<?php
function tampilkanDaftar(iterable $daftar): void
{
    foreach ($daftar as $item) {
        echo "- $item\n";
    }
}

echo "Dari array:\n";
tampilkanDaftar(['Budi', 'Siti']);

echo "Dari ArrayIterator:\n";
tampilkanDaftar(new ArrayIterator(['Andi', 'Rina']));

function namaTambahan(): Generator
{
    yield 'Tono';
    yield 'Tini';
}

echo "Dari generator:\n";
tampilkanDaftar(namaTambahan());

Output yang Dihasilkan:

text
Dari array:
- Budi
- Siti
Dari ArrayIterator:
- Andi
- Rina
Dari generator:
- Tono
- Tini

Fungsi tampilkanDaftar() hanya berisi foreach, dan foreach memang mengerti semua wujud iterable. Generator namaTambahan() bahkan tidak perlu dibuka isinya lebih dulu — nilai-nilainya mengalir satu per satu langsung ke perulangan. Karena kontraknya bertipe iterable, Anda bebas mengganti representasi data di sisi pemanggil tanpa menyentuh kode fungsi.

Perlindungan tipe tetap berlaku ketat. Mengirim nilai yang bukan array maupun Traversable membuat PHP melempar TypeError sebelum baris pertama fungsi berjalan:

php
<?php
function tampilkanDaftar(iterable $daftar): void
{
    foreach ($daftar as $item) {
        echo "- $item\n";
    }
}

tampilkanDaftar('Budi, Siti, Andi');

Output yang Dihasilkan:

text

Fatal error: Uncaught TypeError: tampilkanDaftar(): Argument #1 ($daftar) must be of type Traversable|array, string given, called in /home/z/my-project/php-course/content/php/advanced-object-model/iterable-types/.verify_tmp.php on line 9 and defined in /home/z/my-project/php-course/content/php/advanced-object-model/iterable-types/.verify_tmp.php:2
Stack trace:
#0 /home/z/my-project/php-course/content/php/advanced-object-model/iterable-types/.verify_tmp.php(9): tampilkanDaftar('Budi, Siti, And...')
#1 {main}
  thrown in /home/z/my-project/php-course/content/php/advanced-object-model/iterable-types/.verify_tmp.php on line 2

Pesan error menyebut tipe yang diminta sebagai Traversable|array — ejaan panjang dari iterable. Gagalnya pemanggilan sebelum fungsi berjalan berarti Anda tidak perlu memvalidasi bentuk data secara manual di dalam fungsi.

Mengembalikan iterable #

Tipe iterable juga sah untuk nilai kembalian. Fungsi penghasil data bebas memilih jalan generator maupun jalan array biasa, dan keduanya memenuhi kontrak yang sama:

php
<?php
function buatNomorUrut(int $banyak): iterable
{
    for ($i = 1; $i <= $banyak; $i++) {
        yield $i;
    }
}

foreach (buatNomorUrut(3) as $nomor) {
    echo "Nomor: $nomor\n";
}

function hariKerja(): iterable
{
    return ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat'];
}

echo implode(', ', hariKerja()) . "\n";

Output yang Dihasilkan:

text
Nomor: 1
Nomor: 2
Nomor: 3
Senin, Selasa, Rabu, Kamis, Jumat

Fungsi buatNomorUrut() memuat yield, sehingga PHP otomatis memperlakukannya sebagai generator yang mengembalikan object Generator — dan object tersebut memenuhi kontrak iterable. Sementara itu hariKerja() sekadar mengembalikan array; array juga memenuhi kontrak iterable. Dari sisi pemanggil, keduanya dikonsumsi dengan cara yang sama: foreach.

Memeriksa Nilai dengan is_iterable #

Ketika data datang dari sumber tak terkontrol — hasil json_decode(), misalnya — fungsi is_iterable() memeriksa apakah nilai tersebut layak diiterasi:

php
<?php
function contohGenerator(): Generator
{
    yield 1;
}

var_dump(is_iterable([1, 2, 3]));
var_dump(is_iterable(['nama' => 'Budi']));
var_dump(is_iterable(new ArrayIterator([])));
var_dump(is_iterable(contohGenerator()));
var_dump(is_iterable('Budi'));
var_dump(is_iterable(42));
var_dump(is_iterable(null));
var_dump(is_iterable(new DateTimeImmutable('2026-01-15')));

Output yang Dihasilkan:

text
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)

Array terindeks, array asosiatif, ArrayIterator, dan generator semuanya lolos pemeriksaan. String 'Budi' gagal meskipun karakternya bisa diakses satu per satu dengan perulangan for — string bukan iterable. Object DateTimeImmutable juga gagal karena class tersebut tidak mengimplementasikan Traversable; keanggotaan iterable ditentukan oleh kontrak yang diimplementasikan, bukan oleh kemampuan yang kebetulan mirip.

Generator sebagai Return Type #

Untuk fungsi penghasil deret ada beberapa pilihan tipe kembalian yang bertumpuk: iterable (paling longgar), Traversable (umum untuk iterator non-generator), dan Generator (paling spesifik). Tipe Generator memberi sinyal paling jujur kepada pemanggil:

php
<?php
function deretGenap(int $batas): Generator
{
    for ($i = 2; $i <= $batas; $i += 2) {
        yield $i;
    }
}

foreach (deretGenap(6) as $bilangan) {
    echo "Genap: $bilangan\n";
}

echo 'Jumlah nilai: ' . count(iterator_to_array(deretGenap(6))) . "\n";

Output yang Dihasilkan:

text
Genap: 2
Genap: 4
Genap: 6
Jumlah nilai: 3

Dengan : Generator, pemanggil tahu hasilnya sekali pakai dan menyediakan metode tambahan seperti getReturn() yang kita bahas pada chapter Generators. Sebaliknya, : iterable lebih fleksibel karena Anda bebas mengubah implementasi menjadi array biasa kelak tanpa merusak kontrak. Aturan praktisnya: gunakan iterable pada API yang menjanjikan “bisa di-foreach”, dan Generator saat perilaku khusus generator memang bagian dari janji fungsi.

Kovariansi dan Kontravariansi #

Aturan variansi mengatur bagaimana tipe boleh berubah ketika method di-override. Nilai kembalian boleh dipersempit (covariance): class anak yang mengembalikan array atau Generator tetap sah menggantikan induk yang mengembalikan iterable, karena setiap array memang iterable. Parameter berlaku sebaliknya — hanya boleh diperluas (contravariance): method class anak boleh menerima iterable di tempat induknya menerima array:

php
<?php
abstract class SumberData
{
    abstract public function ambilSemua(): iterable;
}

class SumberArray extends SumberData
{
    public function ambilSemua(): array
    {
        return ['Budi', 'Siti'];
    }
}

class SumberGenerator extends SumberData
{
    public function ambilSemua(): Generator
    {
        yield 'Andi';
        yield 'Rina';
    }
}

function prosesSemua(SumberData $sumber): void
{
    foreach ($sumber->ambilSemua() as $nama) {
        echo "Nama: $nama\n";
    }
}

prosesSemua(new SumberArray());
prosesSemua(new SumberGenerator());

class PenyaringDasar
{
    public function saring(array $data): array
    {
        return array_filter($data);
    }
}

class PenyaringLuas extends PenyaringDasar
{
    public function saring(iterable $data): array
    {
        $hasil = [];
        foreach ($data as $kunci => $nilai) {
            if ($nilai) {
                $hasil[$kunci] = $nilai;
            }
        }
        return $hasil;
    }
}

$penyaring = new PenyaringLuas();
print_r($penyaring->saring(['sedan' => 2, 'motor' => 0, 'bus' => 1]));
print_r($penyaring->saring(new ArrayIterator(['nama' => 'Budi', 'kosong' => ''])));

Output yang Dihasilkan:

text
Nama: Budi
Nama: Siti
Nama: Andi
Nama: Rina
Array
(
    [sedan] => 2
    [bus] => 1
)
Array
(
    [nama] => Budi
)

Class SumberArray menyempitkan nilai kembalian menjadi array, dan SumberGenerator menjadi Generator; keduanya tetap kompatibel dengan SumberData sehingga prosesSemua() bisa mengonsumsi keduanya lewat kontrak iterable. Pada pasangan PenyaringDasar/PenyaringLuas, parameter saring() diperluas dari array menjadi iterable sehingga versi class anak melayani lebih banyak jenis masukan — array maupun iterator. Membalik arah salah satunya, misalnya menyempitkan parameter dari iterable menjadi array pada class anak, berakhir fatal error saat class dimuat.

Ringkasan #

Tipe iterable menyatukan array dan seluruh object Traversable dalam satu kontrak: parameter yang luas, nilai kembalian yang fleksibel, pemeriksaan runtime melalui is_iterable(), dan aturan variansi yang jelas terhadap array maupun Generator. Dengan tiga chapter terakhir — generator, iterator, dan iterable — kotak peralatan pemrosesan data Anda kini lengkap. Selanjutnya kita beralih ke topik yang mengurus hidup-mati object: weak reference yang memantau object tanpa menghalangi garbage collector.

Referensi #