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.
Kısa cevap
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. Money gibi bir değer nesnesini modern PHP’de nasıl tasarladığımı değer nesneleri yazısında anlatmıştım; buradaki soru o nesneyi Eloquent’e hangi kancadan bağlayacağınız.
Neden
-
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. -
Ç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. -
Cast query builder’a uygulanmaz. 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 ve tercihinizden bağımsız olarak geçerlidir.
Ne yapmalı
-
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. 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]; } -
Money’yi immutable tutun ve ona bir
cents()verin. VO’yureadonlyyapı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. Sorgularda kullanmak üzerecents()’i dışa açı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.
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.