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

> Custom cast kullanın — VO ↔ kolon eşlemesi için tam da o var. Tek CastsAttributes sınıfı her modelde çalışır; accessor'lar model başına tekrar demektir. DB'de integer cent, modelde immutable Money.

- Soruldu: 2026-07-29
- Yanıtlandı: 2026-08-04
- Soran: Sıla
- Etiketler: laravel, eloquent, casts
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/integer-cent-olarak-sakladigim-para-alanlari-icin-custom-cast-mi-yoksa/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**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?


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:

```php
/** @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.
