PHP'de referans, aynı değişken içeriğine iki farklı adla erişmenin yoludur: $b = &$a; yazdıktan sonra $a ve $b aynı içeriği paylaşır ve birine yapılan atama diğerinde de görünür. Referanslar üç işlemde kullanılır: referansla atama (=&), referansla parametre geçirme (function f(&$x)) ve referansla değer döndürme (function &f()). Kılavuz, referansların C dilindeki işaretçiler (pointer) olmadığını, bellek adresi tutmadıklarını ve bunun yerine sembol tablosundaki takma adlar olduğunu vurgular.

Bu yazıda referansın ne olduğunu, unset() ile nasıl koparıldığını, fonksiyonlara referansla parametre geçirmeyi, ünlü foreach referans tuzağını, dizi içindeki referansların kopyalamada nasıl davrandığını, referansla döndürmeyi ve nesnelerin neden referans gibi görünüp aslında referans olmadığını anlatıyoruz. Örnekler PHP 8.5.12 ile çalıştırıldı. Serinin tamamı için PHP öğrenme rehberi sayfasına bakabilirsiniz.

Referans nedir: aynı içeriğe iki ad

Referans nedir: aynı içeriğe iki ad

PHP'de değişken adı ile değişken içeriği ayrı şeylerdir. Kılavuzun benzetmesiyle değişken adları bir dizindeki dosya adlarına, içerik ise dosyanın kendisine benzer; referans da Unix'teki sabit bağlantıya (hard link) karşılık gelir. $b = &$a sonrasında $b, $a'yı göstermez; ikisi de aynı yeri gösterir ve tamamen eşit konumdadır.

php
<?php
$a = 'elma';
$b = &$a;
$b = 'armut';
echo $a, PHP_EOL; // çıktı: armut

unset($a);
echo $b, PHP_EOL; // çıktı: armut
var_dump(isset($a)); // çıktı: bool(false)

$x = 1;
$y = $x; // normal atama: kopya
$y = 2;
echo $x, PHP_EOL; // çıktı: 1

Örnekte unset($a) yalnızca $a adını içerikten ayırdı; içerik silinmedi ve $b üzerinden hâlâ erişilebiliyor. Kılavuz bunu Unix'teki unlink çağrısına benzetir: Dosyanın bir adını silmek, başka adı olan dosyayı yok etmez. Son bölüm ise normal atamanın değeri kopyaladığını hatırlatıyor; referans kullanmadığınız sürece PHP'de değişkenler birbirinden bağımsızdır.

Kılavuzun bir notu daha var: Tanımsız bir değişkeni referansla atar, geçirir veya döndürürseniz o değişken oluşturulur. Bu bazen sürpriz yaratır, çünkü bir fonksiyona referansla dizi anahtarı verdiğinizde o anahtar diziye eklenir:

php
<?php
function doldur(&$hedef)
{
}
doldur($yeni);
var_dump($yeni); // çıktı: NULL (uyarı yok, değişken oluştu)

$dizi = [];
doldur($dizi['anahtar']);
var_dump(array_key_exists('anahtar', $dizi)); // çıktı: bool(true)

Referansla parametre geçirme

PHP'de argümanlar varsayılan olarak değerle geçer; fonksiyon içinde parametreyi değiştirmek dışarıdaki değişkeni etkilemez. Fonksiyonun çağıranın değişkenini değiştirmesini istiyorsanız tanımda parametrenin önüne & koyarsınız. Kılavuzun altını çizdiği nokta: İşaret yalnızca fonksiyon tanımında bulunur, çağrıda kullanılmaz. Tanım, argümanın referansla geçmesi için yeterlidir.

php
<?php
function puanEkle(array &$ogrenci, int $puan): void
{
    $ogrenci['puan'] += $puan;
}
$ogr = ['ad' => 'Elif', 'puan' => 70];
puanEkle($ogr, 15);
echo $ogr['puan'], PHP_EOL; // çıktı: 85

function kopyaylaEkle(array $ogrenci, int $puan): array
{
    $ogrenci['puan'] += $puan;
    return $ogrenci;
}
$yeni = kopyaylaEkle($ogr, 5);
echo $ogr['puan'], ' / ', $yeni['puan'], PHP_EOL; // çıktı: 85 / 90

İki yaklaşım aynı sonuca ulaşabilir, ama okunabilirlikleri farklıdır. Referanslı sürümde fonksiyonu çağıran kişi, çağrıya bakarak $ogr değişkeninin değişeceğini anlayamaz; bunu ancak tanımı okuyunca öğrenir. Değer alıp yeni değer döndüren sürümde ise değişiklik atama satırında açıkça görünür. Kılavuz performans için referans kullanmamanızı da önerir; motor bu tür iyileştirmeleri kendisi yapar.

Referanslı parametreye yalnızca bir değişken (veya referans döndüren bir fonksiyonun sonucu) verilebilir. Kılavuz sabit bir değer veya ifade vermenin geçersiz olduğunu belirtir. PHP 8.5.12'de sabit değer vermek şu hatayla sonuçlanır:

php
<?php
function artir(&$n)
{
    $n++;
}
artir(5);
// Fatal error: Uncaught Error: artir(): Argument #1 ($n) could not be passed by reference in /yol/ornek.php:6

PHP'nin kendi fonksiyonlarından bazıları da parametreyi referansla alır ve bu yüzden sonuçlarını size değişken üzerinden verir. sort() diziyi yerinde sıralar, preg_match() eşleşmeleri üçüncü parametreye yazar, str_replace() dördüncü parametreye yapılan değiştirme sayısını koyar:

php
<?php
$sayilar = [3, 1, 2];
sort($sayilar);
echo implode(',', $sayilar), PHP_EOL; // çıktı: 1,2,3

preg_match('/(\d+)-(\d+)/', 'Sipariş 2026-118', $eslesme);
echo $eslesme[2], PHP_EOL; // çıktı: 118

$metin = str_replace('a', 'e', 'kalabalık', $sayi);
echo "$metin ($sayi değişiklik)", PHP_EOL; // çıktı: kelebelık (3 değişiklik)

Bu fonksiyonlara da yalnızca değişken verebilirsiniz; sort([3, 1, 2]) yazmak hata verir. Sıralama fonksiyonları PHP dizi sıralama, düzenli ifadeler preg_match ve regex yazısında anlatılıyor.

foreach referans tuzağı

Referansla en sık yapılan hata foreach döngüsünde görülür. foreach ($dizi as &$v) ile elemanları yerinde değiştirdikten sonra $v döngü bittiğinde de dizinin son elemanına bağlı kalır. Aynı adla ikinci bir döngü kurduğunuzda her tur bu son elemanın üzerine yazar:

php
<?php
$renkler = ['kirmizi', 'yesil', 'mavi'];
foreach ($renkler as &$renk) {
    $renk = strtoupper($renk);
}
foreach ($renkler as $renk) {
}
print_r($renkler);
/* çıktı:
Array
(
    [0] => KIRMIZI
    [1] => YESIL
    [2] => YESIL
)
*/

Adım adım ne olduğuna bakalım: İkinci döngünün her turu, sıradaki değeri $renk değişkenine atar. $renk hâlâ $renkler[2] ile aynı yeri gösterdiği için bu atama son elemanı değiştirir. Birinci turda son eleman 'KIRMIZI', ikinci turda 'YESIL' olur; üçüncü turda ise son eleman kendi değerini, yani 'YESIL''i tekrar kendine yazar. Kılavuz bu davranışı ayrıca not eder: Referanslı bir değişkene foreach içinde değer atanırsa, bağlı olduğu içerik de değişir.

php
<?php
$renkler = ['kirmizi', 'yesil', 'mavi'];
foreach ($renkler as &$renk) {
    $renk = strtoupper($renk);
}
unset($renk); // bağı kopar
foreach ($renkler as $renk) {
}
echo implode(',', $renkler), PHP_EOL; // çıktı: KIRMIZI,YESIL,MAVI

$renkler2 = ['kirmizi', 'yesil', 'mavi'];
foreach ($renkler2 as $i => $r) { // referanssız alternatif
    $renkler2[$i] = ucfirst($r);
}
echo implode(',', $renkler2), PHP_EOL; // çıktı: Kirmizi,Yesil,Mavi

Dizi içindeki referanslar kopyalamada korunur

Dizi içindeki referanslar kopyalamada korunur

Kılavuz, dizilerin içinde referans bulunmasının tehlikeli olabileceği konusunda uyarır. Normal bir atama, sağ tarafta referans olsa bile sol tarafı referans yapmaz. Ancak bir dizinin elemanlarından biri referanssa, dizi kopyalandığında o eleman kopyada da referans olarak kalır. Bu, dizinin değerle bir fonksiyona geçtiği durumlarda da geçerlidir.

php
<?php
$dizi = [1];
$ref = &$dizi[0];  // $dizi[0] artık bir referans kümesinde
$kopya = $dizi;    // normal kopya
$kopya[0]++;
var_dump($dizi[0]); // çıktı: int(2)

$kopya değiştirildiği halde orijinal $dizi de değişti, çünkü sıfırıncı eleman referans olarak kopyalandı. Kılavuzun ifadesiyle dizilerin referans davranışı eleman bazında tanımlanır ve dizi kabının kendisinin referans olup olmamasından bağımsızdır. var_dump() çıktısında referans olan elemanlar & işaretiyle gösterilir; bu, hata ayıklarken beklenmedik referansları yakalamanın kolay bir yoludur:

php
<?php
$x = [1, 2];
$r = &$x[1];
var_dump($x);
text
array(2) {
  [0]=>
  int(1)
  [1]=>
  &int(2)
}

Aynı mekanizma [&$a, &$b[0]] biçiminde dizi oluştururken de çalışır: Dizinin elemanları dışarıdaki değişkenlere bağlanır ve elemanları artırmak o değişkenleri artırır. Bu tür yapılar kısa vadede pratik görünse de kodun izlenmesini zorlaştırır.

php
<?php
$a = 1;
$b = [2, 3];
$arr = [&$a, &$b[0]];
$arr[0]++;
$arr[1]++;
var_dump($a, $b[0]);
/* çıktı:
int(2)
int(3)
*/

Burada $arr dizisinin iki elemanı, dışarıdaki $a değişkenine ve $b dizisinin ilk elemanına bağlı. $arr üzerinden yapılan artırmalar doğrudan o değişkenlere yansıyor. Bu dizi bir fonksiyona değerle geçirilse bile elemanlar referans olarak kalacağı için fonksiyon içindeki değişiklikler de dışarıya sızar. Böyle bir davranışa gerçekten ihtiyacınız yoksa dizi oluştururken & kullanmaktan kaçının ve gerekiyorsa bu durumu kodda bir yorumla açıkça belirtin.

Referansla değer döndürme

Bir fonksiyonun bir değişkenin kopyasını değil, o değişkene bağlanmayı sağlayan bir referans döndürmesi için tanımda fonksiyon adının önüne & konur. Kılavuz, parametre geçirmeden farklı olarak burada işaretin iki yerde gerektiğini belirtir: Fonksiyon tanımında ve sonucun atandığı yerde ($x = &f();). Atamada işaret yoksa yine kopya alırsınız.

php
<?php
class Ayarlar
{
    private array $veri = ['tema' => 'açık'];

    public function &al(string $anahtar)
    {
        return $this->veri[$anahtar];
    }

    public function goster(): string
    {
        return $this->veri['tema'];
    }
}
$ayar = new Ayarlar;
$tema = &$ayar->al('tema');
$tema = 'koyu';
echo $ayar->goster(), PHP_EOL; // çıktı: koyu

$kopya = $ayar->al('tema'); // & yok: kopya
$kopya = 'mavi';
echo $ayar->goster(), PHP_EOL; // çıktı: koyu

Bu örnek aynı zamanda referansla döndürmenin bir riskini gösteriyor: private olarak işaretlenmiş bir özelliğin içeriği, dışarıdan doğrudan değiştirilebilir hale geldi. Kılavuz, referansla döndürmeyi performans için kullanmamanızı ve yalnızca geçerli bir teknik nedeniniz varsa başvurmanızı açıkça önerir. Yalnızca değişkenler referansla döndürülebilir; return ($this->deger); gibi parantezli bir ifade veya üçlü operatör sonucu döndürmeye çalışmak çalışmaz.

Kılavuzdaki ikinci örnek, fonksiyon içindeki bir static diziye dışarıdan erişmeyi gösterir. Aşağıdaki uyarlamada üç farklı kullanım yan yana duruyor: referans atamasıyla eklenen kayıt kalıcı oluyor, işaretsiz atamayla alınan kopyaya eklenen kayıt kayboluyor. Referans bekleyen bir fonksiyona, burada array_push()'a, referans döndüren fonksiyonun sonucu doğrudan verilebiliyor:

php
<?php
function &kayitlar()
{
    static $liste = [];
    return $liste;
}

$ref = &kayitlar();
$ref[] = 'ilk kayıt';

$kopya = kayitlar();           // & yok: kopya
$kopya[] = 'kaybolan kayıt';

array_push(kayitlar(), 'push ile eklenen');
print_r(kayitlar());
/* çıktı:
Array
(
    [0] => ilk kayıt
    [1] => push ile eklenen
)
*/

Kılavuz, array_push(&kayitlar(), ...) gibi çağrı tarafında işaret kullanmanın çalışmadığını ve ölümcül hata ürettiğini de belirtir. Referans döndüren bir fonksiyonun sonucunu başka bir referans parametresine vermek için sonucu olduğu gibi yazmanız yeterlidir. Bu kalıp, fonksiyonun kendi durumunu saklayıp dışarıya kontrollü biçimde açtığı küçük kayıt defterleri ve önbellekler için kullanılabilir; ancak durumu bir sınıfın özelliğinde tutmak genellikle daha okunaklıdır.

global ve static de birer referanstır

Kılavuz, birçok dil yapısının arka planda referans mekanizmasıyla çalıştığını belirtir. global $degisken; yazmak aslında $degisken = &$GLOBALS['degisken']; yazmakla aynıdır. Bu yüzden fonksiyon içinde global değişkene yeni bir referans atarsanız, değişiklik yalnızca fonksiyonun içinde görünür. Global değişkeni gerçekten değiştirmek için $GLOBALS dizisini kullanmanız gerekir:

php
<?php
$mesaj = 'ilk';
$diger = 'ikinci';

function degistir()
{
    global $mesaj, $diger;
    $mesaj = &$diger; // yalnızca yerel bağ değişti
}
degistir();
echo $mesaj, PHP_EOL; // çıktı: ilk

function degistir2()
{
    $GLOBALS['mesaj'] = 'GLOBALS ile değişti';
}
degistir2();
echo $mesaj, PHP_EOL; // çıktı: GLOBALS ile değişti

Aynı nedenle fonksiyon içinde unset($degisken) yazmak global değişkeni silmez, yalnızca yerel bağlantıyı koparır. Global ve static değişkenlerin kapsam kuralları PHP değişkenler ve kapsam yazısında ele alınıyor. Closure'larda use (&$x) ile yapılan yakalama da bir referanstır; ayrıntılar anonim fonksiyonlar yazısında.

Nesneler referans değildir

PHP'de nesne içeren bir değişkeni fonksiyona geçirdiğinizde fonksiyon aynı nesne üzerinde çalışır; bu yüzden nesneler çoğu zaman "referansla geçer" diye anlatılır. Kılavuz bunun doğru olmadığını belirtir: Nesneler işaretçiye benzer bir tutamaçla taşınır, ama bu PHP referansıyla aynı şey değildir. Farkı görmenin en kolay yolu, parametreye yeni bir nesne atamaktır:

php
<?php
class Kullanici
{
    public function __construct(public string $ad) {}
}

function adiDegistir(Kullanici $k): void
{
    $k->ad = 'Mehmet';
}
function yeniNesne(Kullanici $k): void
{
    $k = new Kullanici('Selin');
}
function yeniNesneRef(Kullanici &$k): void
{
    $k = new Kullanici('Selin');
}

$u = new Kullanici('Ahmet');
adiDegistir($u);
echo $u->ad, PHP_EOL; // çıktı: Mehmet
yeniNesne($u);
echo $u->ad, PHP_EOL; // çıktı: Mehmet
yeniNesneRef($u);
echo $u->ad, PHP_EOL; // çıktı: Selin

İlk fonksiyon nesnenin özelliğini değiştirdi ve değişiklik dışarıda göründü, çünkü iki değişken aynı nesneyi tutuyor. İkinci fonksiyon parametreye yeni bir nesne atadı; bu yalnızca yerel değişkeni etkiledi ve $u eski nesneyi tutmaya devam etti. Üçüncü fonksiyon gerçek bir referans aldığı için çağıranın değişkeni yeni nesneye bağlandı. Nesnenin bağımsız bir kopyasına ihtiyacınız varsa clone kullanırsınız; bu konu clone ve nesne karşılaştırma yazısında.

Küçük uygulama: düz listeden ağaç kurmak

Referansların gerçekten işe yaradığı bir senaryo, veritabanından düz bir liste olarak gelen kategorileri iç içe bir ağaca dönüştürmektir. Her düğümü kimliğiyle bir sözlüğe koyup alt düğümleri referansla bağladığınızda, ağaç tek geçişte kurulur ve hangi sırayla bağlandığı önemli olmaz:

php
<?php
$kategoriler = [
    ['id' => 1, 'ust' => null, 'ad' => 'Elektronik'],
    ['id' => 2, 'ust' => 1, 'ad' => 'Telefon'],
    ['id' => 3, 'ust' => 1, 'ad' => 'Bilgisayar'],
    ['id' => 4, 'ust' => 3, 'ad' => 'Dizüstü'],
];
$dugumler = [];
foreach ($kategoriler as $k) {
    $dugumler[$k['id']] = ['ad' => $k['ad'], 'alt' => []];
}
$agac = [];
foreach ($kategoriler as $k) {
    if ($k['ust'] === null) {
        $agac[] = &$dugumler[$k['id']];
    } else {
        $dugumler[$k['ust']]['alt'][] = &$dugumler[$k['id']];
    }
}
unset($dugumler);

function yazdir(array $liste, int $derinlik = 0): void
{
    foreach ($liste as $d) {
        echo str_repeat('  ', $derinlik), $d['ad'], PHP_EOL;
        yazdir($d['alt'], $derinlik + 1);
    }
}
yazdir($agac);
/* çıktı:
Elektronik
  Telefon
  Bilgisayar
    Dizüstü
*/

Dizüstü düğümü, Bilgisayar düğümünün altına bağlandığında Bilgisayar zaten Elektronik'in altına referansla eklenmiş durumdaydı. Referans sayesinde bu sonraki ekleme ağacın her yerinde görünüyor. Kopyayla çalışsaydınız, Elektronik altındaki Bilgisayar kopyası Dizüstü'yü hiç görmeyecekti. İş bitince unset($dugumler) ile yardımcı dizinin adı kaldırılıyor; ağaçtaki içerik ise $agac üzerinden yaşamaya devam ediyor.

Sık yapılan hatalar

Sık yapılan hatalar: Referanslı foreach sonrası unset yapmamak, Çağrıda & kullanmak, Referanslı parametreye sabit vermek, Performans için referans kullanmak
  • Referanslı foreach sonrası unset yapmamak: Son eleman sonraki döngülerde sessizce değişir.
  • Çağrıda & kullanmak: Referans işareti yalnızca fonksiyon tanımına yazılır.
  • Referanslı parametreye sabit vermek: could not be passed by reference hatası alırsınız; önce bir değişkene atayın.
  • Performans için referans kullanmak: Kılavuz bunu önermez; motor kopyalamayı kendisi iyileştirir.
  • Nesneleri referans sanmak: Parametreye yeni nesne atamak çağıranı etkilemez; özellik değiştirmek etkiler.
  • Dizideki referansı unutmak: Referans içeren bir dizinin kopyası, o elemanı orijinalle paylaşır.

Sık Sorulan Sorular

PHP referansı ile C işaretçisi arasındaki fark nedir?

Kılavuz, referansların işaretçi olmadığını açıkça belirtir: İşaretçi aritmetiği yapılamaz ve referanslar gerçek bellek adresleri değildir. Referans, aynı içeriğe bağlı ikinci bir addır. Bu yüzden bir fonksiyon içinde referans parametresini başka bir değişkene yeniden bağlamak, çağıran taraftaki değişkeni o yeni değişkene bağlamaz; yalnızca yerel adı başka yere taşır. Kılavuz, bir fonksiyonun seçtiği bir değişkene bağlanmak istediğinizde referansla döndürmeyi önerir: Fonksiyon hangi değişkene bağlanılacağını döndürür, bağlama işlemini ise çağıran taraf =& ile yapar.

Bir değişkenin referans olup olmadığını nasıl anlarım?

Dil, bunu doğrudan soran basit bir fonksiyon sunmaz. Hata ayıklarken dizilerde var_dump() çıktısındaki & işareti, bir elemanın referans kümesinde olduğunu gösterir. En iyi yaklaşım, referansları yalnızca gerçekten gerektiği yerde ve dar bir kapsamda kullanmak, iş bitince unset() ile kapatmaktır.

Referansla döndüren fonksiyon ne zaman gerekir?

Kılavuz, bir fonksiyonun hangi değişkene bağlanılacağını bulmak için kullanıldığı durumları örnek verir; örneğin bir fonksiyon içindeki static bir koleksiyona dışarıdan doğrudan eleman eklemek. Günlük kodda nadiren gerekir ve kapsüllemeyi bozabilir; çoğu durumda değeri döndürüp gerektiğinde bir ayar metodu yazmak daha anlaşılırdır.

unset bir referansı silerken içeriği de siler mi?

Hayır. unset() yalnızca o adı içerikten ayırır. İçeriğe bağlı başka bir ad varsa içerik onun üzerinden yaşamaya devam eder.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: PHP öğrenme rehberi · PHP döngüler · PHP fonksiyon tanımlama · clone ve nesne karşılaştırma · Değişkenler ve kapsam