İçeriğe geç
Muhammet Şafak
en
Soran: Sıla Cevaplandı:

Integer cent olarak sakladığım para alanları için custom cast mı yoksa accessor/mutator mı kullanmalıyım?


Soru

Tutarları veritabanında integer cent olarak saklıyorum (`price_cents` gibi). Modellerimde bir `Money` value object'i istiyorum ama cent temsili her yere sızmasın — Blade'de, servis katmanında, testlerde sürekli `/100` yapmak istemiyorum. Laravel'de bunu bağlamak için custom cast (`CastsAttributes`) mı yoksa klasik accessor/mutator mu daha doğru? İleride tutarın yanına bir `currency` kolonu da ekleyebilirim; bu tercih o durumu nasıl etkiler?

Cevap

Kısa cevap: Custom cast (CastsAttributes implement eden bir sınıf) kullanın. Cast’lar tam olarak value object ↔ kolon eşlemesi için tasarlandı; accessor/mutator daha eski ve tek kolonluk bir VO için daha hantal bir araçtır.

  1. Custom cast bu iş için biçilmiş kaftandır. get()/set() içeren tek bir cast sınıfı yazar, onu her model ve her kolonda yeniden kullanırsınız. Accessor’lar model-başına, attribute-başına metotlardır; aynı Money mantığını her modelde tekrar yazmak zorunda kalırsınız.

  2. Depolamayı integer cent bırakın. set() içinde saklanacak int cent’i döndürün, get() içinde Money::fromCents($value) kurun. Model size her zaman bir Money verdiği için cent temsili dışarı sızmaz; kod tabanının geri kalanı /100 görmez.

  3. Çok kolonlu senaryoyu cast temiz çözer. Tutar ve currency iki ayrı kolonsa, cast get()/set() içindeki $attributes dizisi üzerinden ikisini birden okuyup yazabilir. set(), tek attribute yerine ['amount_cents' => ..., 'currency' => ...] gibi bir dizi döndürebilir. Accessor/mutator ile iki kolonu tek objeye map edip geri iki kolona yazmak çirkinleşir. İleride currency ekleyecekseniz bu tek başına cast’ı seçtirir.

  4. Money’yi immutable tutun. VO’yu readonly yapın; para aritmetiğini (float’tan kaçınarak) ve formatlamayı orada tutun — Blade’de değil. Eşitlik (equals) ve currency uyuşmazlığı kontrolü de VO’nun işidir, cast’ın değil.

  5. Cast’ın query builder’a uygulanmadığını unutmayın. Cast yalnızca model attribute’larına giriş/çıkışta çalışır; ->where('price', $money) gibi bir sorgu cast’tan geçmez. Filtrelerde hâlâ ham cent geçmeniz gerekir (->where('price', $money->cents())). Bu, insanların en sık takıldığı noktadır; VO’ya bir cents() verip sorgularda onu kullanın.

  6. Kenar durumları unutmayın. Nullable kolon için get()/set() içinde null’ı ele alın; cast her hydration’da çalıştığı için ucuz kalsın. Sabit olmayan parametreleri (varsayılan currency gibi) castUsing üzerinden cast’a geçebilirsiniz.

Tek kolonluk çekirdek şöyle:

/** @implements CastsAttributes<Money, Money> */
final class MoneyCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): ?Money
    {
        return $value === null ? null : Money::fromCents((int) $value);
    }

    public function set($model, string $key, $value, array $attributes): ?int
    {
        return $value?->cents();
    }
}

// modelde:
protected function casts(): array
{
    return ['price' => MoneyCast::class];
}

Sonuç: Ben olsam custom cast + DB’de integer cent + immutable Money VO üçlüsünü kurardım; currency’yi de aynı cast içinden ikinci kolon olarak yönetirdim. Accessor/mutator’ı yalnızca tek modele özgü, VO’ya bile değmeyen basit bir hesaplanmış alan için saklardım. Böylece cent temsili modelin arkasında kalır ve geri kalan her katman yalnızca Money görür.

İlgili Yazılar

Etiketler: #laravel#eloquent#casts
Paylaş:

Yorumlar

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

Diğer Sorular

Tüm sorular

Sitede Ara

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

Esc ile kapat Pagefind ile güçlendirildi