İçeriğe geç
Muhammet Şafak
en
Diller 2 dk okuma

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.

Kapak görseli — koyu masada klavye ile dizüstünün arasında duran gri kupa; üzerinde mor VALUE OBJECT yazısı ve üç küpten oluşan simge

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: ö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

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 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 value object’leri daha temiz yazmanızı sağlıyor — kılavuzun tarif ettiği gibi readonly bir özellik yalnız tanımlandığı sınıfın kapsamından bir kez atanabiliyor, sonrasında değiştirilemiyor:

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 referans nesnelerinde önemli olan referansın kendisi, değer nesnelerinde ise içeriğin değeri.

$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

Bu konuda sorulanlar

Bu konudaki deneyler

Framework'e bağlanmadan çalışan modüler PHP kütüphaneleri; 39 paketlik InitPHP ekosistemine ve Framework3'e dönüştü.

Şu an ne yapıyor

39 paketlik bir PHP kütüphane ailesi: Router, Database, Cache, Mailer, Socket ve diğerleri hem tek başına hem birlikte çalışıyor, hepsi Composer/Packagist üzerinden MIT lisansıyla dağıtılıyor. Framework'e bağlanmadan parça kullanmak isteyen PHP geliştiricisi bugün kurabilir.

Açık kaynak PHP Composer PHPUnit
Ocak 2021 — Ağustos 2022
Etiketler: #PHP
Uzmanlık: PHP Geliştirici
Paylaş:

Son güncelleme:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi