PHP'de bir nesneyi başka bir değişkene atadığınızda nesne kopyalanmaz; iki değişken de aynı nesneyi gösteren tanıtıcının (identifier) birer kopyasını tutar. Gerçek bir kopya için clone kullanılır: $kopya = clone $nesne; tüm özellikleri yüzeysel (shallow) olarak kopyalanmış yeni bir nesne üretir ve sınıfta tanımlıysa __clone() metodunu çalıştırır. Nesneleri karşılaştırırken de iki ayrı operatör vardır: == aynı sınıftan olup özellikleri eşit olan nesneleri eşit sayar, === ise yalnızca aynı nesne örneği için doğrudur.
Bu yazıda nesnelerin neden "referansla geçiyor gibi" davrandığını, yüzeysel kopyanın iç içe nesnelerde yarattığı tuzağı, __clone() ile derin kopya yapmayı, readonly özelliklerin klonlamadaki kurallarını, PHP 8.5 ile gelen clone() fonksiyonunun özellik değiştirerek kopyalama yeteneğini ve nesne karşılaştırma kurallarını çalışan örneklerle anlatıyoruz. Örnekler PHP 8.5.12 ile çalıştırıldı. Serinin tamamı için PHP öğrenme rehberi sayfasına bakabilirsiniz.
Nesneler Gerçekten Referansla mı Geçer?

PHP'de sık duyulan "nesneler varsayılan olarak referansla geçer" sözü kılavuza göre tam doğru değildir. PHP'de referans, iki değişkenin aynı değere yazabilmesini sağlayan bir takma addır ve & ile oluşturulur. Bir nesne değişkeni ise nesnenin kendisini değil, nesneye ulaşmayı sağlayan bir tanıtıcıyı tutar. Nesneyi atadığınızda, fonksiyona argüman olarak verdiğinizde veya döndürdüğünüzde bu tanıtıcı kopyalanır. Değişkenler birbirinin takma adı olmaz, ama aynı nesneyi gösterdikleri için birinden yapılan değişiklik diğerinden de görünür.
<?php
class Sayac
{
public int $deger = 0;
}
function artir(Sayac $s): void
{
$s->deger++; // aynı nesneyi değiştirir
}
function degistir(Sayac $s): void
{
$s = new Sayac(); // yalnızca yerel tanıtıcı değişir
$s->deger = 100;
}
$a = new Sayac();
$b = $a; // tanıtıcının kopyası
$b->deger = 5;
echo $a->deger, "\n"; // çıktı: 5
artir($a);
echo $a->deger, "\n"; // çıktı: 6
degistir($a);
echo $a->deger, "\n"; // çıktı: 6 (değişmedi)
$c = &$a; // gerçek referans: $c, $a değişkeninin takma adı
$c = new Sayac();
echo $a->deger, "\n"; // çıktı: 0 ($a artık yeni nesneyi gösteriyor)Farkı degistir() fonksiyonu ortaya koyuyor. Gerçek bir referans geçilseydi fonksiyon içinde $s değişkenine yeni nesne atamak dışarıdaki $a değişkenini de değiştirirdi. Tanıtıcı kopyalandığı için yalnızca fonksiyonun yerel değişkeni yeni nesneyi gösterdi. Son bölümde & ile oluşturulan gerçek referans ise $a değişkeninin kendisini değiştirdi. Değişken referanslarının ayrıntıları PHP referanslar yazısında anlatılıyor.
clone ile Yüzeysel Kopya
clone anahtar kelimesi yeni bir nesne oluşturur ve kaynak nesnenin tüm özelliklerini ona kopyalar. Kılavuzun vurguladığı nokta, bu kopyanın yüzeysel olmasıdır. Metin, sayı ve dizi gibi değerler bağımsız kopyalanır, ama başka bir nesneyi tutan özellik yalnızca o nesnenin tanıtıcısını kopyalar. Yani klon ile asıl nesne iç içe nesneyi paylaşmaya devam eder. Ayrıca özelliklerden biri bir değişkene referanssa, klondaki özellik de aynı referans olarak kalır.
<?php
class Adres
{
public function __construct(public string $sehir) {}
}
class Musteri
{
public array $etiketler = [];
public function __construct(public string $ad, public Adres $adres) {}
}
$m1 = new Musteri('Ali', new Adres('Ankara'));
$m2 = clone $m1;
$m2->ad = 'Veli'; // bağımsız: metin kopyalandı
$m2->etiketler[] = 'vip'; // bağımsız: dizi kopyalandı
$m2->adres->sehir = 'İzmir'; // PAYLAŞILAN: aynı Adres nesnesi
echo $m1->ad, "\n"; // çıktı: Ali
echo count($m1->etiketler), "\n"; // çıktı: 0
echo $m1->adres->sehir, "\n"; // çıktı: İzmir
var_dump($m1 === $m2); // çıktı: bool(false)
var_dump($m1->adres === $m2->adres); // çıktı: bool(true)İlk iki değişiklik beklendiği gibi yalnızca klonu etkiledi; üçüncüsü ise asıl müşterinin adresini de değiştirdi. Bu, klonlamayla ilgili hataların büyük bölümünün kaynağıdır: bir nesneyi "güvenli bir kopya üzerinde çalışmak" için klonlarsınız, ama kopyanın içindeki nesneler hâlâ ortaktır. Dizilerin içindeki nesneler için de aynı durum geçerlidir; dizi kopyalanır, içindeki nesne tanıtıcıları paylaşılır.
__clone() ile Derin Kopya
Klonlama tamamlandıktan sonra, sınıfta __clone() metodu tanımlıysa PHP bu metodu yeni nesne üzerinde çağırır. Metodun içinde $this, klonu gösterir; paylaşılmaması gereken nesne özelliklerini burada tekrar clone ederek derin kopya elde edersiniz. Kılavuzdaki örnek de tam olarak bunu yapar: üst nesne klonlanırken içindeki nesnelerden biri ayrıca klonlanır, diğeri bilinçli olarak paylaşılır.
<?php
class Adres
{
public function __construct(public string $sehir) {}
}
class Siparis
{
/** @var list<Adres> */
public array $teslimatlar = [];
public function __construct(public Adres $fatura) {}
public function __clone(): void
{
$this->fatura = clone $this->fatura;
$this->teslimatlar = array_map(fn (Adres $a) => clone $a, $this->teslimatlar);
}
}
$s1 = new Siparis(new Adres('Ankara'));
$s1->teslimatlar[] = new Adres('Bursa');
$s2 = clone $s1;
$s2->fatura->sehir = 'İzmir';
$s2->teslimatlar[0]->sehir = 'Antalya';
echo $s1->fatura->sehir, ' / ', $s1->teslimatlar[0]->sehir, "\n"; // çıktı: Ankara / Bursa
echo $s2->fatura->sehir, ' / ', $s2->teslimatlar[0]->sehir, "\n"; // çıktı: İzmir / Antalya__clone() yazarken hangi nesnelerin gerçekten kopyalanması gerektiğine bilinçli karar verin. Bir veritabanı bağlantısı veya paylaşılan bir yapılandırma nesnesi gibi kaynakları kopyalamak genellikle istenmez; kılavuzun açılış örneği de bir pencere kaynağını tutan nesneyi kopyalarken yeni bir kaynak oluşturma ihtiyacından söz eder. __clone() geri değer döndürmez ve diğer sihirli metotların aksine public olmak zorunda değildir.
Bu son ayrıntının pratik bir kullanımı var: __clone() metodunu private yaparsanız sınıfın dışından klonlama engellenir. PHP 8.5.12'de dışarıdan clone denemesi Error: Call to private method Tekil::__clone() from global scope verir. Benzer şekilde enum case'leri hiç klonlanamaz; ayrıntılar enum yazısında. Sihirli metotların genel listesi için sihirli metotlar yazısına bakın.
readonly Özellikler ve Klonlama

Readonly özellikler bir kez atandıktan sonra değiştirilemez. Bu, "değiştirilmiş bir kopya döndür" kalıbını uzun süre zorlaştırdı. PHP 8.3 ile readonly özelliklerin __clone() içinde yeniden atanabilmesine izin verildi. Böylece her kopyaya yeni bir kimlik numarası vermek gibi işler mümkün oldu. PHP 8.4 ile bir kısıt eklendi: __clone() içinde readonly özelliğe referans almak gibi dolaylı değişiklikler artık yasak.
<?php
class Belge
{
private static int $sayac = 0;
public readonly int $no;
public function __construct(public string $baslik)
{
$this->no = ++self::$sayac;
}
public function __clone(): void
{
$this->no = ++self::$sayac; // PHP 8.3+ : readonly yeniden atanabilir
}
}
$b1 = new Belge('Teklif');
$b2 = clone $b1;
echo $b1->no, ' ', $b2->no, "\n"; // çıktı: 1 2PHP 8.5: clone() ile Özellik Değiştirerek Kopyalama
PHP 8.5 ile clone bir fonksiyon olarak da kullanılabilir hale geldi. İkinci parametre $withProperties, özellik adlarını kopyanın alacağı yeni değerlere eşleyen bir dizidir. Yalnızca listelenen özellikler değişir, diğerleri kopyalandıkları değeri korur. Kılavuza göre bu değerler __clone() çalıştıktan sonra uygulanır, tüm özelliklerin görünürlüğüne uyar ve readonly özellikler, asıl nesnede zaten atanmış olsalar bile kopyada güncellenebilir. Bu bölümdeki kodlar PHP 8.5 veya üstünü gerektirir; birçok sunucu hâlâ daha eski sürümlerle çalıştığı için hedef sürümünüzü kontrol edin.
<?php
final class Para
{
public function __construct(
public readonly int $kurus,
public readonly string $birim = 'TRY',
) {}
public function ekle(int $kurus): static
{
return clone($this, ['kurus' => $this->kurus + $kurus]);
}
}
$fiyat = new Para(1000);
$yeni = $fiyat->ekle(250);
echo $fiyat->kurus, ' ', $yeni->kurus, ' ', $yeni->birim, "\n"; // çıktı: 1000 1250 TRY
try {
clone($fiyat, ['kurus' => 5]);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
// çıktı: Cannot modify protected(set) readonly property Para::$kurus from global scopeİkinci deneme görünürlük kuralını gösteriyor. Readonly bir özellik, sınıfın dışından yazılamaz; clone() da bu kurala uyduğu için global kapsamdan readonly özelliği değiştiremez. Bu yüzden özellik değiştirerek kopyalama genellikle yukarıdaki ekle() gibi bir "with" metodunun içinde kullanılır. Geçersiz bir tip verdiğinizde ise normal atama kuralları işler: clone($this, ['kurus' => 'x']) çağrısı TypeError: Cannot assign string to property Para::$kurus of type int fırlatır.
PHP 8.4 ve öncesinde aynı sonucu almak için readonly olmayan bir özelliği klonlayıp değiştirmeniz ya da PHP 8.3'ten itibaren __clone() içinde yeniden atama yapmanız gerekirdi. Readonly olmayan özelliklerde en sade eski yol şudur:
<?php
final class Filtre
{
public function __construct(public string $siralama = 'ad', public int $sayfa = 1) {}
public function sayfaya(int $sayfa): static
{
$kopya = clone $this; // PHP 8.4 ve öncesi uyumlu
$kopya->sayfa = $sayfa;
return $kopya;
}
}
$f1 = new Filtre();
$f2 = $f1->sayfaya(3);
echo $f1->sayfa, ' ', $f2->sayfa, ' ', $f2->siralama, "\n"; // çıktı: 1 3 adDeğiştirilemez nesnelerin PHP'deki en bilinen örneği DateTimeImmutable sınıfıdır: modify() gibi metotları nesneyi değiştirmek yerine yeni bir nesne döndürür. Değiştirilebilir DateTime nesnesini başka bir değişkene atayıp değiştirmek ise asıl tarihi de değiştirir; bu klasik hatanın ayrıntıları DateTime yazısında. Görünürlük ve readonly kuralları için public, private, protected ve readonly yazısına bakın.
Tek İfadede Klonlamak
Kılavuzun gösterdiği küçük ama kullanışlı bir yazım daha var: yeni klonlanmış bir nesnenin üyesine aynı ifade içinde erişebilirsiniz. (clone $tarih)->modify('+1 day') yazdığınızda asıl nesneye dokunmadan, kopyası üzerinde işlem yapıp sonucu kullanırsınız. Bu, değiştirilebilir bir nesneden geçici olarak türetilmiş bir değer gerektiğinde ara değişken açmaktan kurtarır. PHP 8.5'te clone bir fonksiyon olduğu için first-class callable sözdizimiyle clone(...) biçiminde de verilebilir; örneğin bir nesne dizisinin her elemanını array_map(clone(...), $liste) ile kopyalayabilirsiniz. Bu ikinci kullanım da PHP 8.5 gerektirir.
<?php
$bugun = new DateTime('2026-11-07');
echo (clone $bugun)->modify('+1 day')->format('d.m.Y'), "\n"; // çıktı: 08.11.2026
echo $bugun->format('d.m.Y'), "\n"; // çıktı: 07.11.2026
$liste = [new ArrayObject([1]), new ArrayObject([2])];
$kopyalar = array_map(clone(...), $liste); // PHP 8.5+
var_dump($kopyalar[0] == $liste[0], $kopyalar[0] === $liste[0]);
/* çıktı:
bool(true)
bool(false)
*/İlk iki satır, değiştirilebilir DateTime nesnesinin klon sayesinde korunduğunu gösteriyor. Son satırda ise kopyaların içerik olarak eşit ama farklı nesneler olduğunu, bir sonraki bölümün konusu olan iki karşılaştırma operatörüyle görüyoruz.
Nesneleri Karşılaştırmak: == ve ===

Kılavuzdaki kurallar kısadır. == ile iki nesne, aynı sınıfın örnekleriyse ve özellikleri aynı değerlere sahipse eşittir; özellik değerleri de == ile karşılaştırılır, yani iç içe nesneler de aynı kurala göre özyinelemeli olarak karşılaştırılır. === ile iki nesne değişkeni ancak aynı sınıfın aynı örneğini gösteriyorsa özdeştir. Eklentiler kendi nesneleri için farklı == kuralları tanımlayabilir.
<?php
class Adres
{
public function __construct(public string $sehir) {}
}
class Konum
{
public function __construct(public string $sehir) {}
}
$a = new Adres('Ankara');
$b = new Adres('Ankara');
$c = $a;
var_dump($a == $b); // çıktı: bool(true) aynı sınıf, aynı değerler
var_dump($a === $b); // çıktı: bool(false) farklı örnekler
var_dump($a === $c); // çıktı: bool(true) aynı örnek
var_dump($a == new Adres('İzmir')); // çıktı: bool(false) farklı değer
var_dump($a == new Konum('Ankara')); // çıktı: bool(false) farklı sınıf
var_dump(spl_object_id($a) === spl_object_id($c)); // çıktı: bool(true)| Karşılaştırma | Ne zaman true? | Kullanım |
|---|---|---|
| $a == $b | Aynı sınıf ve tüm özellikler == ile eşit | Değer nesnelerinde içerik eşitliği |
| $a === $b | Aynı nesne örneği | Kimlik kontrolü, önbellek, tekil nesneler |
| spl_object_id($a) === spl_object_id($b) | Aynı nesne örneği (nesneler yaşarken) | Nesneyi dizi anahtarı gibi kullanmak |
spl_object_id() her nesne için, nesne yaşadığı sürece benzersiz bir tam sayı döndürür. Kılavuzun uyardığı gibi nesne yok edildikten sonra aynı numara başka bir nesneye verilebilir; bu yüzden bu numarayı uzun ömürlü bir kimlik olarak saklamayın. Karşılaştırma operatörlerinin genel davranışı için eşitlik ve karşılaştırma yazısına bakın.
Özyinelemeli == karşılaştırmasının bir bedeli olduğunu da akılda tutun: büyük nesne ağaçlarında her özellik tek tek gezilir. İki değişkenin aynı nesneyi gösterip göstermediğini öğrenmek istiyorsanız === hem doğru anlamı taşır hem de içerik karşılaştırması yapmaz.
WeakMap: Nesneye Bağlı, Nesneyle Ölen Önbellek
Nesnelerle bilgi ilişkilendirmek istediğinizde, örneğin bir nesneden hesaplanmış pahalı bir sonucu saklamak için, PHP 8.0 ile gelen WeakMap kullanışlıdır. WeakMap nesneleri anahtar olarak kabul eden bir sözlüktür; SplObjectStorage sınıfına benzer ama anahtar olarak tutulan nesnenin referans sayısını artırmaz. Nesneye başka hiçbir yerden başvuru kalmadığında nesne çöp toplayıcı tarafından temizlenir ve haritadan da kendiliğinden düşer. Kılavuz bu yapının asıl kullanım alanını, nesneden türetilen ve nesneden daha uzun yaşaması gerekmeyen verilerin önbelleği olarak tanımlar. WeakMap ArrayAccess, Countable ve IteratorAggregate uyguladığı için çoğu durumda ilişkisel bir dizi gibi kullanılabilir. Diğer SPL veri yapıları için SPL veri yapıları yazısına bakabilirsiniz.
<?php
$onbellek = new WeakMap();
$rapor = new stdClass();
$onbellek[$rapor] = 'hesaplanmış sonuç';
var_dump(count($onbellek)); // çıktı: int(1)
unset($rapor); // nesneye başka başvuru kalmadı
var_dump(count($onbellek)); // çıktı: int(0)Uygulamalı Mini Örnek: Sepeti Güvenle Kopyalamak
Bir e-ticaret sepetini "taslak" olarak kopyalayıp kullanıcının kopya üzerinde denemeler yapmasına izin vermek istediğinizi düşünün. Sepet, kalem nesnelerinden oluşan bir dizi tutuyor. Yüzeysel kopya, kopyadaki adet değişikliğinin asıl sepete de yansımasına yol açar. Aşağıdaki örnek önce sorunu, sonra __clone() ile çözümünü gösteriyor.
<?php
final class Kalem
{
public function __construct(public string $urun, public int $adet) {}
}
class Sepet
{
/** @var list<Kalem> */
public array $kalemler = [];
public function ekle(string $urun, int $adet): void
{
$this->kalemler[] = new Kalem($urun, $adet);
}
public function ozet(): string
{
return implode(', ', array_map(fn (Kalem $k) => "$k->urun x$k->adet", $this->kalemler));
}
}
class GuvenliSepet extends Sepet
{
public function __clone(): void
{
$this->kalemler = array_map(fn (Kalem $k) => clone $k, $this->kalemler);
}
}
foreach ([Sepet::class, GuvenliSepet::class] as $sinif) {
$asil = new $sinif();
$asil->ekle('Defter', 2);
$taslak = clone $asil;
$taslak->kalemler[0]->adet = 10;
$taslak->ekle('Kalem', 1);
echo $sinif, ' => asıl: ', $asil->ozet(), ' | taslak: ', $taslak->ozet(), "\n";
}
/* çıktı:
Sepet => asıl: Defter x10 | taslak: Defter x10, Kalem x1
GuvenliSepet => asıl: Defter x2 | taslak: Defter x10, Kalem x1
*/İlk satırda dizinin kendisi kopyalandığı için yeni eklenen Kalem yalnızca taslakta görünüyor, ama mevcut kalemin adedi asıl sepette de 10 oldu, çünkü iki dizi aynı Kalem nesnesini gösteriyordu. GuvenliSepet her kalemi ayrıca klonladığı için asıl sepet hiç etkilenmedi. Bu ayrımı test etmek için kopyayı değiştirip asıl nesneyi kontrol eden küçük bir deneme yazmak, derin kopya gerektiren her sınıfta iyi bir alışkanlıktır.
Sık Yapılan Hatalar
- Atamanın kopya ürettiğini sanmak:
$b = $a;nesneyi kopyalamaz. Bağımsız bir nesne içinclonekullanın. - Yüzeysel kopyayı derin sanmak:
cloneiç içe nesneleri ve dizilerdeki nesneleri paylaşır. Gerekirse__clone()içinde bunları da klonlayın. - __clone() metodunun asıl nesnede çalıştığını sanmak: Metot yeni kopya üzerinde çalışır;
$thisklonu gösterir. - Nesne eşitliği için === beklemek: İçerikleri aynı iki ayrı nesne
===ile eşit değildir; içerik eşitliği için==kullanın. - clone() fonksiyonunu eski sürümde kullanmak: Özellik değiştirerek kopyalama PHP 8.5 gerektirir. Eski sürümlerde klonlayıp sonra atayın.
- DateTime nesnesini paylaşmak: Değiştirilebilir tarih nesnesini başka yerde değiştirmek asıl değeri de bozar;
DateTimeImmutabletercih edin.
Sık Sorulan Sorular
clone ile new arasındaki fark nedir?
new kurucu metodu çalıştırarak sıfırdan bir nesne oluşturur. clone ise kurucuyu çalıştırmaz; mevcut nesnenin özelliklerini kopyalar ve varsa yalnızca __clone() metodunu çağırır.
Klonlanmış nesnede __clone() içinde özellik değiştirebilir miyim?
Evet, normal özellikler her zaman değiştirilebilir. Readonly özellikler PHP 8.3'ten itibaren __clone() içinde yeniden atanabilir. PHP 8.5'teki clone($nesne, [...]) ile verilen değerler ise __clone() bittikten sonra uygulanır.
İki nesnenin aynı nesne olduğunu nasıl anlarım?
=== operatörü bu iş içindir. Nesneyi bir dizide anahtar olarak kullanmanız gerekiyorsa spl_object_id() değerini kullanabilirsiniz, ama nesne yok edildikten sonra numaranın başka bir nesneye verilebileceğini unutmayın.
Derin kopya için serialize ve unserialize kullanmak doğru mu?
Bu yöntem tüm nesne ağacını kopyalar ama kaynakları, closure'ları ve özel serileştirme mantığı olan sınıfları doğru taşımayabilir ve her sınıfın ne kopyalayacağına sizin karar vermenizi engeller. Kılavuzun klonlama bölümünde anlatılan yol, neyin kopyalanacağına sınıfın kendisinin karar verdiği __clone() metodudur.
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 referanslar · PHP sınıf ve nesne · PHP 8.5 yenilikleri · PHP DateTime ve tarih işlemleri