# readonly value object'lerimde "withX" tarzı kopyalama metotlarını nasıl temiz yazarım?

> `readonly`'de withX yeni instance döndürür: taşınabilir yol `new self` + named arguments, PHP 8.5'te doğrulama basitse `clone($this, [...])`.

- Soruldu: 2026-08-25
- Yanıtlandı: 2026-08-28
- Soran: Kaan
- Etiketler: php, oop
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/readonly-value-objectlerimde-withx-tarzi-kopyalama-metotlarini-nasil-temiz-yazarim/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Domain katmanımda para (Money) ve adres (Address) gibi value object'lerim var ve bunları PHP 8.2'nin `readonly` özelliğiyle immutable yaptım. Amacım nesne bir kez kurulduktan sonra hiç değişmemesi.

Ama immutable kopyalar üretmeye çalışırken sürekli "Cannot modify readonly property" hatası alıyorum. Örneğin bir `withAmount()` metodunda property'yi güncellemeye çalışıyorum ve patlıyor. Bu "withX" tarzı kopyalama metotlarını temiz ve kendini tekrar etmeden nasıl yazarım?


Kısa cevap: `readonly`'de mutasyon yoktur; kopyalarken property'yi değiştirmeye çalışmak yerine yeni bir instance dönmelisiniz.

## Kısa cevap

Aldığınız hata tam da bunu söylüyor — `$this->amount = ...` yasaktır, çünkü readonly property yalnızca kurulduğu scope'ta bir kez set edilebilir. `readonly`'nin domain modellerine ne kazandırdığını [PHP 8.1 yazısında](/tr/blog/php-8-1-yolda-enumlar-ve-readonly-ile-daha-guvenli-modeller/) anlatmıştım; withX deseni de o garantinin doğal sonucu.

## Neden

1. **Kök neden.** readonly property'yi ilk atamadan sonra clone içinde bile (sıradan bir metotta) yeniden yazamazsınız; runtime bunu bilinçli olarak engeller. Yani withX'in içinde `$this`'i değiştirmek imkânsız. (Küçük bir düzeltme: `readonly` PHP 8.2 değil, **PHP 8.1** ile geldi.)

2. **PHP 8.3+: `__clone` yalnızca deep-clone içindir.** 8.3 ile readonly property'leri `__clone` içinde yeniden başlatabilirsiniz, ama bu withX'e dışarıdan yeni bir değer taşımaz; asıl amacı iç içe nesneleri (örneğin bir `DateTimeImmutable`) derin kopyalamaktır. withX'i bununla çözmeye çalışmayın.

3. **İki yolun doğrulama davranışı farklıdır.** `new self` her kopyayı constructor'dan geçirdiği için doğrulamayı (negatif tutar, geçersiz currency) bedavaya alırsınız. `clone($this, [...])` ise constructor'ı çağırmaz; invariant kontrolünüz varsa clone yolunda onu elle tetiklemeniz gerekir.

## Ne yapmalı

1. **Taşınabilir ve temiz desen: `new self` + named arguments.** Her withX, constructor'ı yeni değerlerle çağırır; böylece tüm invariant'lar tekrar doğrulanır. PHP 8.1'den beri her yerde çalışır:

   ```php
   public function withAmount(int $amount): static
   {
       return new self(amount: $amount, currency: $this->currency);
   }
   ```

2. **Alan çoğaldıkça named arguments tekrarı kırar.** Çok alanlı bir VO'da her withX'te sadece değişeni yazar, gerisini `$this->...` ile taşırsınız; named arguments sıraya bağımlılığı ortadan kaldırır ve okunurluğu korur.

3. **PHP 8.5'teyseniz wither'ı tek satıra indirin.** 8.5, kopyalarken property override etmenizi sağlar:

   ```php
   public function withAmount(int $amount): static
   {
       return clone($this, ['amount' => $amount]);
   }
   ```

**Sonuç:** Ben olsam PHP 8.2/8.3'te `new self` + named arguments ile giderdim — hem taşınabilir hem de her kopyayı doğrulamadan geçiriyor. 8.5'e geçtiyseniz ve doğrulama basitse `clone($this, [...])` daha zarif; ama invariant'larınız katıysa, o güveni kaybetmemek için ben yine constructor tabanlı yolu tercih ederdim.

## İlgili Yazılar

- [Modern PHP'de value object tasarlamak](/tr/blog/modern-phpde-deger-nesneleri-value-object-tasarlamak/) — Blog
- [PHP 8.1 yolda: enum'lar ve readonly ile daha güvenli modeller](/tr/blog/php-8-1-yolda-enumlar-ve-readonly-ile-daha-guvenli-modeller/) — Blog
- [Eşzamanlı HTTP çağrıları için ham Fiber mı, ReactPHP/AMPHP mı, yoksa Swoole mu seçmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/eszamanli-http-cagrilari-ham-fiber-reactphp-amphp-swoole/) — Sor Bakalım
- [Integer cent olarak sakladığım para alanları için custom cast mı yoksa accessor/mutator mı kullanmalıyım?](https://muhammetsafak.com/tr/sor-bakalim/integer-cent-olarak-sakladigim-para-alanlari-icin-custom-cast-mi-yoksa/) — Sor Bakalım
- [Tip güvenli koleksiyonlar için PHPStan generics mi kullanmalıyım, yoksa her tür için ayrı sınıf mı yazmalıyım?](https://muhammetsafak.com/tr/sor-bakalim/tip-guvenli-koleksiyonlar-icin-phpstan-generics-mi-kullanmaliyim-yoksa-her-tur/) — Sor Bakalım
