readonly value object'lerimde "withX" tarzı kopyalama metotlarını nasıl temiz yazarım?
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?
Cevap
Kısa cevap: readonly’de mutasyon yoktur; kopyalarken property’yi değiştirmeye çalışmak yerine yeni bir instance dönmelisiniz. 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.
- 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:readonlyPHP 8.2 değil, PHP 8.1 ile geldi.) - 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:public function withAmount(int $amount): static { return new self(amount: $amount, currency: $this->currency); } - 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. - PHP 8.3+:
__cloneyalnızca deep-clone içindir. 8.3 ile readonly property’leri__cloneiç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 birDateTimeImmutable) derin kopyalamaktır. withX’i bununla çözmeye çalışmayın. - PHP 8.5: wither için tam bu var. 8.5, kopyalarken property override etmenizi sağlar; wither deseni tek satıra iner:
public function withAmount(int $amount): static { return clone($this, ['amount' => $amount]); } - Doğrulamayı unutmayın.
new selfher 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.
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
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.