# Modern PHP'de value object tasarlamak

> İlkel tipler yerine anlamı koda gömmek: PHP'de value object tasarımının neden ve nasılı üzerine.

- Yayın: 2022-05-01
- Kategori: Diller
- Etiketler: PHP
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/modern-phpde-deger-nesneleri-value-object-tasarlamak/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Bir kullanıcının e-posta adresini `string` olarak saklıyoruz. Sipariş tutarını `float` olarak tutuyoruz. Koordinatları iki ayrı `float` olarak taşıyoruz. Bu yaklaşım işe yarıyor; ama bir maliyeti var: o string'in gerçekte ne anlama geldiğini, hangi kurallara tabi olduğunu, nerede geçerli ya da geçersiz sayıldığını kodun kendisi bilmiyor.

Value object, bu anlam kaybının çözümü. Kavram DDD (Domain-Driven Design — Alan Odaklı Tasarım) literatüründen geliyor ama pratikte herhangi bir projede uygulanabilir. Özü basit: bir kavramı temsil eden, kimliği değil değeri önemli olan, değişmez (immutable) nesneler.

## İlkel takıntısı (primitive obsession) neden sorun

Kodda ilkel tip kullanımı çoğaldığında birkaç şey kaçınılmaz oluyor:

Doğrulama mantığı çoğalıyor ve dağılıyor. E-posta doğrulama bir controller'da, bir form request'te, belki bir model event'ında yazılıyor. Hangisi doğru? Hepsi? Hiçbiri?

Tip sistemi size yardım edemiyor. `createOrder(string $email, float $amount)` imzasına bakarak bu iki string'in birbirinin yerine geçip geçemeyeceğini anlayamazsınız. Derleme zamanında da, IDE'de de.

Kural değiştiğinde (e-posta artık farklı bir kurala tabi) tüm kullanım yerlerini bulmak gerekiyor. Gözden kaçan her yer bir hata.

## Basit bir value object: Email

```php
final class Email
{
    private string $value;

    public function __construct(string $value)
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException(
                "Geçersiz e-posta adresi: {$value}"
            );
        }
        $this->value = strtolower($value);
    }

    public function value(): string
    {
        return $this->value;
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }
}
```

Bu nesne oluşturulamadan hatalı bir e-posta sisteme giremez. Doğrulama tek yerde yaşıyor. `Email` alan bir fonksiyon imzası, o parametrenin ne olduğunu açıkça söylüyor.

## PHP 8.1 readonly ile immutability'yi güçlendirmek

[PHP 8.1 ile gelen `readonly` özelliği](/blog/php-8-1-yolda-enumlar-ve-readonly-ile-daha-guvenli-modeller) value object'leri daha temiz yazmanızı sağlıyor:

```php
final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {
        if ($amount < 0) {
            throw new \InvalidArgumentException('Para miktarı negatif olamaz.');
        }
        if (strlen($currency) !== 3) {
            throw new \InvalidArgumentException('Para birimi 3 harfli ISO kodu olmalıdır.');
        }
    }

    public function add(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \LogicException('Farklı para birimleri toplanamaz.');
        }
        return new self($this->amount + $other->amount, $this->currency);
    }

    public function equals(self $other): bool
    {
        return $this->amount === $other->amount
            && $this->currency === $other->currency;
    }
}
```

`Money` burada immutable. `add()` yeni bir nesne döndürüyor, mevcut nesneyi değiştirmiyor. Bu value object'lerin temel kuralı: bir kez oluşturulduktan sonra içi değişmez, yeni bir değer istiyorsanız yeni bir nesne oluşturursunuz.

## Eşitlik kimlikle değil değerle ölçülür

Değer nesnelerinin ayırt edici özelliği eşitliğin nasıl tanımlandığı. İki farklı `Email` nesnesi aynı e-posta adresini taşıyorsa eşittir — hangi nesne referansına sahip olduğunuz önemli değil. Bu, varlıkların (entity) kimliğe göre eşitlik kurmasından temel olarak farklı.

```php
$email1 = new Email('test@example.com');
$email2 = new Email('TEST@EXAMPLE.COM');

$email1->equals($email2); // true — her ikisi de normalleştirildi
```

## Value object'leri ne zaman kullanmalı

Her ilkel tipi sarmak gerekmez. Şu sorular yol gösterici:

- Bu değerin belirli bir geçerlilik kuralı var mı?
- Bu değer için iş mantığı (business logic) yazılması gerekiyor mu?
- Kodun farklı yerlerinde aynı doğrulama tekrarlanıyor mu?
- Bu tipin yanlış yere geçirilmesi derleyici veya IDE tarafından yakalanabiliyor mu?

Bu sorulardan birine bile "evet" yanıtı verildiyse value object muhtemelen değer yaratacak.

Koordinatlar, para miktarı, e-posta, telefon numarası, renk kodu, URL — bunların hepsi value object adayı. Öte yandan kullanıcının adı veya bir sayfa başlığı gibi yalnızca string olan ve iş kuralı taşımayan alanlar için bu yapıya gerek yok.

## Sonuç

Value object'ler kodu uzatır ama anlam kaybını önler. Bir metot imzasına bakıp o metodun ne aldığını, hangi kurallar altında çalıştığını anlamak, ilkel tip yığınına kıyasla çok daha hızlı. Bu hız birikimi uzun soluklu projelerde ciddi bir bakım kolaylığına dönüşüyor.

Modern PHP — readonly property'ler, constructor promotion, isimlendirme yetkinliği — bu kalıbı yazmayı her zamankinden kolay hale getiriyor. Gerekçe zaten yıllardır yerinde; araçlar da artık yeterli.
