Cast, accessor & mutator
Database hanya mengenal angka dan teks. Cast adalah lapisan yang membuat kodemu bekerja dengan enum, tanggal, dan objek nilai — bukan dengan string yang harus ditebak artinya.
Intisari
- Cast didefinisikan di method
casts(), dan berlaku dua arah: saat membaca dan saat menyimpan. datetimemenghasilkan objek Carbon, bukan string. Semua aritmetika tanggal jadi mudah.encrypteddanencrypted:arraymengenkripsi di database — tapi kolomnya jadi tidak bisa dicari.- Accessor menghitung nilai turunan; mutator merapikan nilai sebelum disimpan.
- Custom cast mengubah beberapa kolom jadi satu objek nilai (Uang, Alamat) — sangat berguna di aplikasi besar.
Cast bawaan yang paling sering dipakai
protected function casts(): array
{
return [
'aktif' => 'boolean',
'harga' => 'integer',
'atribut' => 'array', // kolom JSON ↔ array PHP
'pengaturan' => AsCollection::class, // ↔ Collection, bukan array biasa
'terbit_pada' => 'datetime', // ↔ Carbon
'lahir' => 'date:Y-m-d',
'status' => StatusProduk::class, // ↔ enum PHP
'nomor_ktp' => 'encrypted',
'metadata' => 'encrypted:array',
'kode' => 'hashed', // di-hash saat disimpan
];
}
$produk->terbit_pada->diffForHumans(); // "3 hari lalu" — karena ini Carbon
$produk->terbit_pada->isPast();
$produk->atribut['warna'] = 'merah'; // array biasa
$produk->status->bisaDibatalkan(); // method milik enum
if ($produk->status === StatusProduk::Aktif) { // perbandingan identitas, bukan string
// ...
}
Enum yang di-cast menghapus seluruh kelas bug typo. Tanpanya, kamu membandingkan
$produk->status === 'aktiv' dan PHP dengan senang hati menjawab false tanpa
keluhan. Dengan cast enum, salah ketik jadi error saat kompilasi maupun saat analisis statis di Fase 6.
Accessor: nilai turunan
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function hargaFormat(): Attribute
{
return Attribute::make(
get: fn () => 'Rp'.number_format($this->harga, 0, ',', '.'),
);
}
protected function tersedia(): Attribute
{
return Attribute::make(
get: fn () => $this->stok > 0 && $this->status === StatusProduk::Aktif,
);
}
{{ $produk->harga_format }} {{-- camelCase di PHP → snake_case saat dipakai --}}
Accessor tidak bisa dipakai di where(). Ia dihitung di PHP setelah baris diambil, jadi
database tidak tahu apa-apa tentangnya. Produk::where('tersedia', true) akan gagal karena tidak ada
kolom bernama itu. Untuk menyaring, pakai scope; untuk menampilkan, pakai accessor. Keduanya sering perlu ada
berdampingan, dan itu wajar.
Mutator: merapikan sebelum disimpan
protected function email(): Attribute
{
return Attribute::make(
get: fn (string $nilai) => $nilai,
set: fn (string $nilai) => strtolower(trim($nilai)),
);
}
protected function nomorTelepon(): Attribute
{
return Attribute::make(
set: fn (string $nilai) => Str::of($nilai)
->replaceMatches('/\D/', '') // buang semua non-digit
->replaceStart('0', '62') // normalkan ke format internasional
->value(),
);
}
Manfaatnya: normalisasi terjadi di satu tempat. Nomor telepon yang masuk lewat form, lewat impor CSV, atau lewat API pihak ketiga semuanya berakhir dalam bentuk yang sama — tanpa perlu diingat oleh siapa pun yang menulis kode pemanggilnya.
Custom cast: beberapa kolom jadi satu objek
final readonly class Uang
{
public function __construct(
public int $jumlah, // dalam satuan terkecil
public string $mataUang = 'IDR',
) {}
public function tambah(Uang $lain): self
{
if ($lain->mataUang !== $this->mataUang) {
throw new InvalidArgumentException('Mata uang berbeda.');
}
return new self($this->jumlah + $lain->jumlah, $this->mataUang);
}
}
class UangCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes): Uang
{
return new Uang(
(int) $attributes['harga_jumlah'],
$attributes['harga_mata_uang'],
);
}
public function set($model, $key, $value, $attributes): array
{
return [
'harga_jumlah' => $value->jumlah,
'harga_mata_uang' => $value->mataUang,
];
}
}
protected function casts(): array
{
return ['harga' => UangCast::class];
}
$produk->harga = new Uang(25_000);
$total = $produk->harga->tambah($ongkir); // menjumlah rupiah dengan dolar = error
Ini pola yang membedakan aplikasi besar. Selama harga hanyalah int, tidak ada yang
mencegahmu menjumlahkannya dengan ongkir berdenominasi lain atau membaginya jadi pecahan yang tidak ada di
dunia nyata. Objek nilai memindahkan aturan itu ke tempat yang tidak bisa dilewati.
Konsekuensi encrypted
| Kemampuan | Kolom biasa | Kolom encrypted |
|---|---|---|
where('kolom', $nilai) | Bisa | Tidak — cipherteks berbeda tiap kali |
| Index | Bisa | Tidak berguna |
order by | Bisa | Mengurutkan cipherteks — tidak berarti |
| Terbaca saat database bocor | Ya | Tidak, selama APP_KEY aman |
Karena itu, pola yang umum untuk data yang perlu dicari sekaligus dilindungi adalah menyimpan dua kolom: satu terenkripsi untuk ditampilkan, satu berisi hash untuk dicocokkan.
Latihan: tambahkan cast enum dan datetime pada model Produk, lalu buat
accessor harga_format. Setelah itu coba Produk::where('harga_format', 'Rp25.000')->first(),
baca pesan errornya, dan jelaskan pada dirimu sendiri kenapa itu memang tidak mungkin berhasil.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.