# 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ı; Email örneği, readonly ile immutability ve değerle eşitlik.

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

---
> **Kısaca:** Value object, eşitliği kimliğine değil değerine bağlı olan değişmez bir nesnedir. Yazı Email ve Money value object'lerinin PHP'de ilkel tiplerin yerini nasıl aldığını, readonly özelliklerin immutability'yi nasıl sağladığını ve bir ilkel tipi sarmanın ne zaman değdiğini gösteriyor.

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. [Martin Fowler tanımı iki cümleye sığdırıyor](https://martinfowler.com/bliki/ValueObject.html): özelliklerinin değeri sayesinde eşit olan nesnelere değer nesnesi denir ve "değer nesneleri değişmez olmalıdır".

## İ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;
    }
}
```

Doğrulamayı [`FILTER_VALIDATE_EMAIL`](https://www.php.net/manual/en/filter.constants.validation.php#constant.filter-validate-email) yapıyor; 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](/tr/blog/php-8-1-yolda-enumlar-ve-readonly-ile-daha-guvenli-modeller/) value object'leri daha temiz yazmanızı sağlıyor — [kılavuzun tarif ettiği gibi](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties) `readonly` bir özellik yalnız tanımlandığı sınıfın kapsamından bir kez atanabiliyor, sonrasında değiştirilemiyor:

```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ı; [Fowler'ın ayrımıyla](https://martinfowler.com/bliki/ValueObject.html) referans nesnelerinde önemli olan referansın kendisi, değer nesnelerinde ise içeriğin değeri.

```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.

---

### Kaynaklar

- [Martin Fowler: ValueObject](https://martinfowler.com/bliki/ValueObject.html)
- [PHP Manual: Readonly properties](https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties)
- [PHP Manual: FILTER_VALIDATE_EMAIL](https://www.php.net/manual/en/filter.constants.validation.php#constant.filter-validate-email)
