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.
-
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. -
Depolamayı integer cent bırakın.
set()içinde saklanacak int cent’i döndürün,get()içindeMoney::fromCents($value)kurun. Model size her zaman birMoneyverdiği için cent temsili dışarı sızmaz; kod tabanının geri kalanı/100görmez. -
Çok kolonlu senaryoyu cast temiz çözer. Tutar ve currency iki ayrı kolonsa, cast
get()/set()içindeki$attributesdizisi ü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. -
Money’yi immutable tutun. VO’yu
readonlyyapı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. -
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 bircents()verip sorgularda onu kullanın. -
Kenar durumları unutmayın. Nullable kolon için
get()/set()içindenull’ı 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
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.