Perulangan

Perulangan #

Perulangan adalah salah satu konstruksi paling mendasar dalam pemrograman — menjalankan blok kode berulang kali tanpa harus menulisnya berkali-kali. PHP menyediakan empat jenis perulangan: while, do...while, for, dan foreach, masing-masing dengan konteks penggunaan yang berbeda. Yang membedakan kode perulangan yang baik dari yang buruk bukan hanya sintaksnya — tapi memilih jenis loop yang paling ekspresif untuk situasi tertentu, menghindari jebakan referensi di foreach, tahu kapan break dan continue tepat digunakan, dan mengenal kapan fungsi array seperti array_map atau array_filter lebih bersih dari loop eksplisit.

Peta Perulangan PHP #

Sebelum masuk ke detail masing-masing, ini gambaran besar kapan menggunakan setiap jenis loop:

flowchart TD
    A{Apa yang di-iterasi?} --> B{Array atau Iterable?}
    B -- Ya --> C[foreach\nPaling idiomatik untuk array]
    B -- Tidak --> D{Jumlah iterasi\ndiketahui sebelumnya?}
    D -- Ya --> E[for\nJika butuh indeks numerik eksplisit]
    D -- Tidak --> F{Harus jalan\nminimal sekali?}
    F -- Ya --> G[do-while\nInput validation, retry loop]
    F -- Tidak --> H[while\nKondisi diperiksa di awal]

    style C fill:#dcfce7
    style E fill:#dbeafe
    style G fill:#fef9c3
    style H fill:#fef9c3

while — Kondisi Diperiksa di Awal #

while adalah perulangan paling sederhana: selama kondisi bernilai true, blok kode dijalankan. Kondisi diperiksa sebelum setiap iterasi — jika kondisi sudah false sejak awal, blok tidak pernah dijalankan sama sekali.

<?php
// Pola dasar
$i = 0;
while ($i < 5) {
    echo $i . " ";
    $i++;
}
// Output: 0 1 2 3 4

while paling tepat digunakan ketika jumlah iterasi tidak diketahui sebelumnya dan bergantung pada kondisi yang bisa berubah kapan saja selama loop berjalan.

Membaca File Baris per Baris #

<?php
// while sangat idiomatik untuk membaca stream — jumlah baris tidak diketahui
$handle = fopen('data.csv', 'r');

if ($handle === false) {
    throw new \RuntimeException("Gagal membuka file");
}

$baris = 0;
while (($line = fgets($handle)) !== false) {
    $baris++;
    $data = str_getcsv(trim($line));
    // proses setiap baris...
    echo "Baris $baris: " . implode(', ', $data) . "\n";
}

fclose($handle);
echo "Total: $baris baris diproses";

Polling dan Retry #

<?php
// Retry dengan backoff eksponensial — jumlah percobaan tidak pasti
$maxPercobaan = 5;
$percobaan    = 0;
$berhasil     = false;

while ($percobaan < $maxPercobaan && !$berhasil) {
    $percobaan++;

    try {
        $response = kirimRequest('https://api.example.com/data');
        $berhasil = true;
        echo "Berhasil di percobaan ke-$percobaan";
    } catch (\Exception $e) {
        if ($percobaan < $maxPercobaan) {
            $delay = 2 ** $percobaan; // 2, 4, 8, 16 detik
            echo "Gagal, coba lagi dalam {$delay}s...\n";
            sleep($delay);
        }
    }
}

if (!$berhasil) {
    throw new \RuntimeException("Gagal setelah $maxPercobaan percobaan");
}

Jebakan: Infinite Loop #

<?php
// ANTI-PATTERN: lupa increment/decrement menyebabkan infinite loop
$i = 0;
while ($i < 10) {
    echo $i;
    // lupa $i++; — loop tidak pernah berhenti!
}

// ANTI-PATTERN: kondisi yang tidak pernah jadi false
$data = ambilData();
while ($data) {
    prosesData($data);
    // lupa assign $data berikutnya — infinite loop!
}

// BENAR: pastikan kondisi pasti berubah menuju false
$data = ambilData();
while ($data !== null) {
    prosesData($data);
    $data = ambilDataBerikutnya(); // kondisi pasti bergerak menuju null
}

do...while — Eksekusi Minimal Satu Kali #

do...while membalik urutan: blok kode dijalankan dulu, baru kondisi diperiksa di akhir. Ini menjamin blok selalu dieksekusi minimal satu kali, terlepas dari nilai kondisi awal.

<?php
// Sintaks dasar
$i = 0;
do {
    echo $i . " ";
    $i++;
} while ($i < 5);
// Output: 0 1 2 3 4

// Bahkan jika kondisi sudah false dari awal, tetap jalan sekali
$i = 100;
do {
    echo "Ini tetap tereksekusi meski $i > 5\n";
    $i++;
} while ($i < 5);
// Output: "Ini tetap tereksekusi meski 100 > 5"

do...while paling tepat untuk validasi input dan operasi yang butuh satu eksekusi dulu sebelum bisa mengecek kondisi:

<?php
// Validasi input interaktif — minta input dulu, cek setelah dapat
do {
    echo "Masukkan angka antara 1-10: ";
    $input = (int) trim(fgets(STDIN));

    if ($input < 1 || $input > 10) {
        echo "Input tidak valid, coba lagi.\n";
    }
} while ($input < 1 || $input > 10);

echo "Input valid: $input\n";

// Proses halaman dengan pagination — ambil halaman pertama dulu,
// cek apakah ada halaman berikutnya
$halaman  = 1;
$semua    = [];

do {
    $hasil   = ambilData($halaman, $perHalaman = 100);
    $semua   = array_merge($semua, $hasil['data']);
    $halaman++;
} while ($halaman <= $hasil['total_halaman']);

echo "Total data: " . count($semua);

for — Iterasi Terstruktur dengan Indeks #

for adalah loop yang paling terstruktur — inisialisasi, kondisi, dan ekspresi langkah semuanya ditulis dalam satu baris. Paling tepat ketika jumlah iterasi diketahui sebelumnya dan kamu membutuhkan akses ke indeks numerik.

<?php
// Sintaks dasar: for (inisialisasi; kondisi; langkah)
for ($i = 0; $i < 5; $i++) {
    echo $i . " ";
}
// Output: 0 1 2 3 4

// Tiga bagian sepenuhnya opsional — semua bisa dikosongkan
// (tapi setidaknya ada exit condition agar tidak infinite)
$i = 0;
for (; $i < 5; ) {  // inisialisasi dan langkah dipindah ke luar/dalam
    echo $i . " ";
    $i++;
}

// Loop mundur
for ($i = 10; $i >= 0; $i--) {
    echo $i . " ";
}
// Output: 10 9 8 7 6 5 4 3 2 1 0

// Langkah lebih dari 1
for ($i = 0; $i <= 100; $i += 10) {
    echo $i . " ";
}
// Output: 0 10 20 30 40 50 60 70 80 90 100

for dengan Array — Ketika Butuh Indeks #

<?php
$items = ['apel', 'mangga', 'jeruk', 'anggur', 'semangka'];

// Akses elemen dengan indeks
for ($i = 0; $i < count($items); $i++) {
    echo "$i: {$items[$i]}\n";
}

// ANTI-PATTERN: count() dipanggil ulang setiap iterasi — boros
for ($i = 0; $i < count($items); $i++) { /* ... */ }

// BENAR: simpan panjang array di luar loop
$panjang = count($items);
for ($i = 0; $i < $panjang; $i++) {
    echo "$i: {$items[$i]}\n";
}

// for berguna ketika perlu akses dua indeks sekaligus
for ($i = 0, $j = count($items) - 1; $i < $j; $i++, $j--) {
    // swap elemen dari depan dan belakang
    [$items[$i], $items[$j]] = [$items[$j], $items[$i]];
}
// Hasil: array dibalik urutannya
print_r($items); // ['semangka', 'anggur', 'jeruk', 'mangga', 'apel']

Loop Bersarang dengan for #

<?php
// Tabel perkalian — loop bersarang yang wajar untuk dua dimensi
for ($baris = 1; $baris <= 5; $baris++) {
    for ($kolom = 1; $kolom <= 5; $kolom++) {
        printf("%4d", $baris * $kolom);
    }
    echo "\n";
}
/*
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25
*/

// Menghasilkan matriks 2D
$matriks = [];
for ($i = 0; $i < 3; $i++) {
    for ($j = 0; $j < 3; $j++) {
        $matriks[$i][$j] = $i * 3 + $j + 1;
    }
}
// [[1,2,3], [4,5,6], [7,8,9]]

foreach — Iterasi Array dan Iterable #

foreach adalah cara paling idiomatik untuk mengiterasi array di PHP. Tidak perlu manajemen indeks manual — PHP mengurus semua itu secara internal.

<?php
// Array terindeks — hanya nilai
$buah = ['apel', 'mangga', 'jeruk'];
foreach ($buah as $item) {
    echo $item . "\n";
}

// Array terindeks — indeks dan nilai
foreach ($buah as $indeks => $item) {
    echo "$indeks: $item\n";
}
// 0: apel  1: mangga  2: jeruk

// Array asosiatif — key dan value
$harga = ['apel' => 5000, 'mangga' => 8000, 'jeruk' => 6000];
foreach ($harga as $nama => $nilai) {
    echo "$nama: Rp " . number_format($nilai) . "\n";
}

// Array multidimensi — destrukturing langsung
$users = [
    ['id' => 1, 'nama' => 'Budi', 'role' => 'admin'],
    ['id' => 2, 'nama' => 'Siti', 'role' => 'editor'],
    ['id' => 3, 'nama' => 'Dani', 'role' => 'viewer'],
];

foreach ($users as ['id' => $id, 'nama' => $nama, 'role' => $role]) {
    echo "[$id] $nama$role\n";
}

foreach Bekerja pada Salinan #

foreach secara default bekerja pada salinan array, bukan array aslinya. Memodifikasi $item di dalam loop tidak mengubah array:

<?php
$angka = [1, 2, 3, 4, 5];

// ANTI-PATTERN: modifikasi $item tidak mengubah array asli
foreach ($angka as $item) {
    $item *= 2; // modifikasi salinan, bukan elemen asli
}
print_r($angka); // [1, 2, 3, 4, 5] — tidak berubah!

// Untuk mengubah elemen array, ada tiga cara:

// Cara 1: gunakan reference (&)
foreach ($angka as &$item) {
    $item *= 2;
}
unset($item); // WAJIB! lepas reference setelah loop
print_r($angka); // [2, 4, 6, 8, 10]

// Cara 2: modifikasi via indeks (lebih eksplisit)
foreach ($angka as $i => $item) {
    $angka[$i] = $item * 2;
}

// Cara 3: array_map (paling fungsional, tidak mengubah asli)
$angkaKali2 = array_map(fn($n) => $n * 2, $angka);

Jebakan Reference di foreach #

Ini adalah salah satu bug paling halus di PHP dan menjadi sumber kebingungan bahkan bagi developer berpengalaman:

<?php
$data = [1, 2, 3, 4, 5];

foreach ($data as &$nilai) {
    $nilai *= 2;
}
// Setelah loop: $nilai masih bereferensi ke elemen TERAKHIR array ($data[4])

// Sekarang $nilai adalah alias untuk $data[4]
// Loop foreach berikutnya atau assignment ke $nilai akan mengubah $data[4]!
foreach ($data as $nilai) {  // $nilai sekarang jadi alias $data[4]
    // setiap iterasi, $nilai di-assign nilai elemen saat ini
    // sekaligus MENGUBAH $data[4] karena $nilai masih bereferensi ke sana!
}

print_r($data);
// Hasil: [2, 4, 6, 8, 8] — elemen terakhir jadi 8 (bukan 10!)
// Ini adalah bug yang sangat sulit ditemukan

// SOLUSI: selalu unset() reference setelah foreach dengan &
foreach ($data as &$nilai) {
    $nilai *= 2;
}
unset($nilai); // putus referensi — wajib!

// Sekarang aman untuk loop berikutnya
foreach ($data as $nilai) {
    echo $nilai . " "; // 2 4 6 8 10 — benar
}
Selalu panggil unset($var) setelah foreach yang menggunakan referensi (&$var). Tanpanya, variabel loop masih menunjuk ke elemen terakhir array sebagai alias. Loop atau assignment berikutnya yang menggunakan nama variabel yang sama akan memodifikasi elemen terakhir tersebut secara tidak terduga — bug yang sangat sulit dilacak.

foreach dengan Object #

foreach bekerja pada semua object yang mengimplementasikan interface Traversable, termasuk Iterator dan IteratorAggregate:

<?php
// Object biasa — foreach mengiterasi public properties
class Konfigurasi
{
    public string  $host    = 'localhost';
    public int     $port    = 3306;
    public string  $dbname  = 'mydb';
    private string $password = 'rahasia'; // private — tidak diiterasi
}

$config = new Konfigurasi();
foreach ($config as $key => $value) {
    echo "$key: $value\n";
}
// host: localhost
// port: 3306
// dbname: mydb
// (password tidak muncul — private)

// ArrayObject — array yang bisa dipakai seperti object
$ao = new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3]);
foreach ($ao as $key => $val) {
    echo "$key => $val\n";
}

break dan continue — Mengendalikan Alur Loop #

break — Keluar dari Loop #

break menghentikan loop sepenuhnya dan melanjutkan eksekusi setelah blok loop:

<?php
// Cari elemen pertama yang memenuhi syarat
$produk = ['laptop', 'mouse', 'keyboard', 'monitor', 'webcam'];
$target  = null;

foreach ($produk as $item) {
    if (str_contains($item, 'key')) {
        $target = $item;
        break; // berhenti begitu ditemukan, tidak perlu iterasi sisanya
    }
}

echo $target; // "keyboard"

// break dengan argumen numerik — keluar dari N level loop bersarang
$matriks = [[1,2,3],[4,5,6],[7,8,9]];
$cari    = 5;
$ditemukan = false;

foreach ($matriks as $baris => $koloms) {
    foreach ($koloms as $kolom => $nilai) {
        if ($nilai === $cari) {
            echo "Ditemukan di baris $baris, kolom $kolom\n";
            $ditemukan = true;
            break 2; // keluar dari KEDUA loop sekaligus
        }
    }
}

continue — Lewati Iterasi Ini #

continue melewati sisa iterasi saat ini dan langsung melompat ke iterasi berikutnya:

<?php
// Proses hanya item yang valid, lewati yang tidak valid
$orders = [
    ['id' => 1, 'total' => 150000, 'status' => 'aktif'],
    ['id' => 2, 'total' => 0,      'status' => 'aktif'],   // lewati
    ['id' => 3, 'total' => 75000,  'status' => 'batal'],   // lewati
    ['id' => 4, 'total' => 200000, 'status' => 'aktif'],
];

$totalPendapatan = 0;

foreach ($orders as $order) {
    if ($order['total'] <= 0) {
        continue; // lewati order dengan total 0 atau negatif
    }

    if ($order['status'] !== 'aktif') {
        continue; // lewati order yang tidak aktif
    }

    $totalPendapatan += $order['total'];
}

echo "Total pendapatan: Rp " . number_format($totalPendapatan); // 350.000

// continue dengan argumen — lompat ke iterasi loop luar
for ($i = 0; $i < 3; $i++) {
    for ($j = 0; $j < 3; $j++) {
        if ($j === 1) {
            continue 2; // lewati iterasi loop luar (bukan hanya inner)
        }
        echo "($i,$j) ";
    }
}
// Output: (0,0) (1,0) (2,0) — kolom 1 dan 2 tidak pernah tercetak

break vs continue vs return #

<?php
// break  — hentikan loop, lanjut setelah loop
// continue — lewati iterasi ini, lanjut ke iterasi berikutnya
// return — keluar dari FUNGSI (sekaligus menghentikan loop)

function cariUser(array $users, int $targetId): ?array
{
    foreach ($users as $user) {
        if ($user['id'] === $targetId) {
            return $user; // langsung return — tidak perlu flag atau break
        }
    }
    return null;
}

// return di dalam loop adalah cara paling bersih untuk "cari dan kembalikan"
// karena tidak butuh variabel flag tambahan

Sintaks Alternatif untuk Template #

Seperti if, foreach dan for juga memiliki sintaks alternatif yang lebih bersih untuk file template HTML:

<!-- foreach dengan sintaks alternatif -->
<ul>
<?php foreach ($produk as $item): ?>
    <li>
        <strong><?= htmlspecialchars($item['nama']) ?></strong>
        — Rp <?= number_format($item['harga']) ?>
    </li>
<?php endforeach; ?>
</ul>

<!-- for dengan sintaks alternatif -->
<?php for ($i = 1; $i <= $totalHalaman; $i++): ?>
    <a href="?page=<?= $i ?>" class="<?= $i === $halamanAktif ? 'active' : '' ?>">
        <?= $i ?>
    </a>
<?php endfor; ?>

<!-- while dengan sintaks alternatif -->
<?php while ($row = $stmt->fetch()): ?>
    <tr>
        <td><?= $row['id'] ?></td>
        <td><?= htmlspecialchars($row['nama']) ?></td>
    </tr>
<?php endwhile; ?>

Generator — Iterasi Data Besar secara Efisien #

Untuk data yang sangat besar (file jutaan baris, query database yang menghasilkan ribuan baris), memuat semua data ke array di memori sebelum iterasi bisa menyebabkan memory exhaustion. Generator memungkinkan kamu menghasilkan nilai satu per satu menggunakan keyword yield:

<?php
// ANTI-PATTERN: baca seluruh file ke array dulu — boros memory
function bacaFileLamaV1(string $path): array
{
    return file($path); // load SEMUA baris ke memori sekaligus
}

foreach (bacaFileLamaV1('data_besar.csv') as $baris) {
    proses($baris);
}
// Jika file 1GB → PHP butuh 1GB+ RAM

// BENAR: generator — hanya satu baris di memori setiap saat
function bacaFileGenerator(string $path): Generator
{
    $handle = fopen($path, 'r');

    if ($handle === false) {
        throw new \RuntimeException("Gagal membuka: $path");
    }

    try {
        while (($baris = fgets($handle)) !== false) {
            yield trim($baris); // hasilkan satu baris, pause, tunggu next()
        }
    } finally {
        fclose($handle); // selalu tutup meski terjadi exception
    }
}

// Pemakaian identik dengan array biasa — bisa pakai foreach
foreach (bacaFileGenerator('data_besar.csv') as $nomor => $baris) {
    prosesCSV($baris);
    // Hanya satu baris di memori setiap saat — tidak peduli ukuran file
}

Generator untuk Range Besar #

<?php
// range(1, 1000000) membuat array 1 juta elemen di memori
// Generator menghasilkan satu nilai setiap kali diminta
function rangeGenerator(int $mulai, int $akhir, int $langkah = 1): Generator
{
    for ($i = $mulai; $i <= $akhir; $i += $langkah) {
        yield $i;
    }
}

// Iterasi 1 juta angka tanpa membuat array 1 juta elemen
$total = 0;
foreach (rangeGenerator(1, 1_000_000) as $angka) {
    $total += $angka;
}
echo $total; // 500000500000

// Generator dengan key-value
function inventoriGenerator(PDO $db): Generator
{
    $stmt = $db->query("SELECT id, nama, stok FROM produk");

    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        yield $row['id'] => $row; // key => value
    }
}

foreach (inventoriGenerator($db) as $id => $produk) {
    if ($produk['stok'] < 10) {
        echo "Stok menipis: {$produk['nama']} ($id)\n";
    }
}

Loop vs Fungsi Array — Kapan Mana #

PHP memiliki serangkaian fungsi array yang secara internal melakukan iterasi. Sering kali lebih bersih dan lebih ekspresif dari loop eksplisit:

<?php
$produk = [
    ['nama' => 'Laptop',  'harga' => 15000000, 'stok' => 5],
    ['nama' => 'Mouse',   'harga' => 250000,   'stok' => 0],
    ['nama' => 'Monitor', 'harga' => 5000000,  'stok' => 12],
    ['nama' => 'Keyboard','harga' => 800000,   'stok' => 3],
];

// ANTI-PATTERN: loop eksplisit untuk transformasi/filter sederhana
$namaProduk = [];
foreach ($produk as $p) {
    $namaProduk[] = $p['nama'];
}

$tersedia = [];
foreach ($produk as $p) {
    if ($p['stok'] > 0) {
        $tersedia[] = $p;
    }
}

$totalNilai = 0;
foreach ($produk as $p) {
    $totalNilai += $p['harga'] * $p['stok'];
}

// BENAR: fungsi array lebih ringkas dan ekspresif
$namaProduk = array_column($produk, 'nama');
$tersedia   = array_filter($produk, fn($p) => $p['stok'] > 0);
$totalNilai = array_reduce($produk, fn($carry, $p) => $carry + ($p['harga'] * $p['stok']), 0);

// Transformasi dengan array_map
$hargaDenganPpn = array_map(
    fn($p) => [...$p, 'harga_ppn' => $p['harga'] * 1.11],
    $produk
);

Panduan memilih antara loop eksplisit dan fungsi array:

Gunakan fungsi array (array_map, array_filter, array_reduce) jika:
  ✓ Transformasi sederhana: ubah setiap elemen ke bentuk lain (map)
  ✓ Filter: pilih subset elemen berdasarkan kondisi (filter)
  ✓ Agregasi: hitung total, rata-rata, gabung (reduce)
  ✓ Hasilnya array baru — tidak ubah array asli

Gunakan loop eksplisit jika:
  ✓ Logika terlalu kompleks untuk satu ekspresi arrow function
  ✓ Perlu break/continue — keluar lebih awal berdasarkan kondisi
  ✓ Side effect: operasi seperti insert ke DB, kirim email, tulis file
  ✓ Perlu akses ke state dari iterasi sebelumnya
  ✓ Multiple operasi berbeda pada satu elemen di satu pass

Ringkasan #

  • while untuk kondisi yang tidak diketahui sebelumnya — kondisi diperiksa di awal, blok mungkin tidak pernah dijalankan. Cocok untuk membaca stream, polling, dan retry loop.
  • do...while untuk blok yang harus dieksekusi minimal satu kali — kondisi diperiksa di akhir. Paling idiomatik untuk validasi input dan pagination.
  • for ketika jumlah iterasi diketahui dan butuh kontrol indeks numerik eksplisit — termasuk untuk loop mundur, langkah tidak sama dengan 1, atau dua indeks sekaligus.
  • foreach adalah cara paling idiomatik untuk mengiterasi array dan iterable — hindari for dengan indeks manual jika foreach sudah cukup.
  • foreach bekerja pada salinan — modifikasi $item di dalam loop tidak mengubah array. Gunakan &$item untuk modifikasi langsung, dan selalu unset($item) setelahnya untuk menghindari bug referensi yang halus.
  • break N dan continue N memungkinkan keluar dari atau melompat N level loop bersarang sekaligus.
  • Generator (yield) adalah solusi untuk iterasi data besar — hanya satu elemen di memori setiap saat, tanpa memuat seluruh dataset ke array.
  • Fungsi array (array_map, array_filter, array_reduce, array_column) lebih ringkas dan ekspresif dari loop eksplisit untuk transformasi, filter, dan agregasi sederhana.

← Sebelumnya: Seleksi Kondisi   Berikutnya: Fungsi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact