Salah satu sumber bug paling umum di PHP adalah salah paham soal penyalinan array: variabel yang dianggap salinan ternyata terikat ke array yang sama, atau sebaliknya — fungsi yang diharapkan mengubah array asli justru menerima salinannya. Kebetulan PHP punya aturan yang tegas di sini: array adalah tipe bernilai (value type), sehingga penugasan menyalin. Chapter ini membedah perilaku tersebut dari contoh paling sederhana sampai jebakan foreach berbasis reference, supaya Anda selalu tahu variabel mana yang benar-benar berubah.
Penugasan Menyalin Array #
Saat tanda sama dengan mengikat array ke variabel baru, PHP membuat salinan yang sepenuhnya independen. Perubahan pada salah satu variabel tidak pernah terlihat dari variabel lainnya:
<?php
$keranjang = ['apel' => 3, 'jeruk' => 5];
$salinan = $keranjang;
$salinan['apel'] = 100;
echo $keranjang['apel'] . "\n";
echo $salinan['apel'] . "\n";Output yang Dihasilkan:
3
100Setelah penugasan, $salinan dan $keranjang adalah dua array terpisah. Menimpa $salinan['apel'] hanya mengubah salinan, sehingga $keranjang['apel'] tetap 3 dan $salinan['apel'] menjadi 100. Aturan ini berlaku seragam untuk semua bentuk penugasan — termasuk hasil fungsi seperti array_map yang selalu mengembalikan array baru.
Salinan Bersarang Tetap Terisolasi #
Aturan penyalinan berlaku sampai ke kedalaman array multidimensional. Ini bagian yang sering membingungkan developer yang datang dari bahasa lain, jadi mari kita buktikan langsung:
<?php
$pesanan = [
'pengirim' => ['nama' => 'Budi', 'kota' => 'Bandung'],
'total' => 150000,
];
$salinan = $pesanan;
$salinan['pengirim']['kota'] = 'Surabaya';
$salinan['total'] = 200000;
echo $pesanan['pengirim']['kota'] . "\n";
echo $salinan['pengirim']['kota'] . "\n";
echo $pesanan['total'] . "\n";Output yang Dihasilkan:
Bandung
Surabaya
150000Menulis ke $salinan['pengirim']['kota'] — sebuah array bersarang dua tingkat — tidak mengubah $pesanan. Secara diam-diam PHP menerapkan copy-on-write (CoW): penugasan mula-mula hanya membuat kedua variabel berbagi struktur data internal yang sama supaya murah, lalu begitu salah satunya ditulis, engine menyalin bagian yang diperlukan sebelum menulis. Hasil yang bisa Anda amati selalu konsisten: menulis ke salinan tidak pernah bocor ke array asli, di kedalaman mana pun. Perilaku ini spesifik array — object PHP justru disalin sebagai penunjuk, sehingga menyalin object sungguhan memakai operator clone yang hanya berlaku untuk object.
Reference Mengikat Dua Nama ke Array Sama #
Jika yang Anda inginkan justru dua variabel yang menunjuk array yang sama, gunakan tanda & saat penugasan. Setelah itu, $b dan $a adalah nama alternatif untuk satu wadah nilai:
<?php
$stok = ['apel' => 3];
$salinan = &$stok;
$salinan['apel'] = 100;
echo $stok['apel'] . "\n";
$stok['jeruk'] = 5;
echo $salinan['jeruk'] . "\n";Output yang Dihasilkan:
100
5Kedua baris tercetak 100 dan 5 karena penulisan lewat nama mana pun menulis ke array yang sama. Perlu dicatat bahwa unset($salinan) hanya melepas ikatan nama tersebut; variabel $stok beserta isinya tetap hidup. Reference juga bisa dilepas dari fungsi — pembahasan lengkapnya ada di halaman References
dokumentasi PHP.
Foreach Berbasis Reference #
foreach bisa memakai &$nilai agar loop mengubah elemen array secara langsung, tanpa perlu menulis kembali lewat indeks:
<?php
$nilai = [50, 60, 70];
foreach ($nilai as &$n) {
$n += 10;
}
unset($n);
print_r($nilai);Output yang Dihasilkan:
Array
(
[0] => 60
[1] => 70
[2] => 80
)Setiap elemen dinaikkan 10 poin karena $n adalah reference ke elemen asli, bukan salinannya. Baris unset($n) setelah loop adalah kebiasaan yang penting: setelah loop selesai, $n masih terikat ke elemen terakhir array, dan tulisan tak sengaja pada $n di kode berikutnya akan mengubah elemen itu.
Jebakan Klasik: Reference yang Terlupakan #
Berikut bug legendaris yang muncul ketika unset() dilupakan dan loop kedua memakai nama variabel yang sama:
<?php
$daftar = [1, 2, 3];
foreach ($daftar as &$v) {
}
foreach ($daftar as $v) {
}
print_r($daftar);Output yang Dihasilkan:
Array
(
[0] => 1
[1] => 2
[2] => 2
)Loop pertama tidak melakukan apa pun selain meninggalkan $v terikat ke elemen terakhir array. Loop kedua lalu menyalin setiap elemen ke $v — dan pada iterasi terakhir, penulisan nilai 2 ke $v mengalir lewat reference ke elemen ketiga, menghasilkan [1, 2, 2]. Perbaikannya satu baris saja:
<?php
$daftar = [1, 2, 3];
foreach ($daftar as &$v) {
}
unset($v);
foreach ($daftar as $v) {
}
print_r($daftar);Output yang Dihasilkan:
Array
(
[0] => 1
[1] => 2
[2] => 3
)Dengan unset($v) segera setelah loop pertama, ikatan reference terputus dan loop kedua kembali berperilaku normal, menghasilkan [1, 2, 3].
Array Masuk ke Function Sebagai Salinan #
Parameter function menerima array sebagai salinan secara default, sehingga perubahan di dalam function tidak kelihatan dari pemanggil. Untuk mengubah array asli, deklarasikan parameternya sebagai reference dengan &:
<?php
function tambahPoin(array $siswa): array
{
$siswa['poin'] += 100;
return $siswa;
}
function naikkanPoin(array &$siswa): void
{
$siswa['poin'] += 100;
}
$profil = ['nama' => 'Budi', 'poin' => 50];
$profilBaru = tambahPoin($profil);
echo $profil['poin'] . ' vs ' . $profilBaru['poin'] . "\n";
naikkanPoin($profil);
echo $profil['poin'] . "\n";Output yang Dihasilkan:
50 vs 150
150Function tambahPoin() bekerja pada salinan: $profil tetap 50 dan hanya $profilBaru yang bernilai 150. Sebaliknya, naikkanPoin() menerima reference sehingga 100 poin langsung tertambah ke $profil asli. Dua gaya ini bukan sekadar sintaks berbeda — gaya mutasi lewat reference lebih sulit dilacak karena efeknya tidak terlihat di lokasi pemanggilan.
Ringkasan #
Penugasan array menyalin secara efektif — dijaga copy-on-write hingga kedalaman berapa pun — sehingga Anda bisa menyalin array besar tanpa takut bocor perubahan. Reference (&) mengikat dua nama ke satu array: berguna untuk foreach pengubah elemen dan parameter function mutasi, tetapi menuntut unset() yang disiplin. Berbekal pemahaman ini, kita siap menyusuri cara terstruktur untuk menjelajahi isi array pada Chapter Array Iteration.