Setiap driver database punya kebiasaan sendiri dalam mengembalikan nilai: SQLite mengembalikan angka, JSON tersimpan sebagai string, dan timestamp tersimpan sebagai teks 2026-01-10 08:00:00. Tanpa bantuan, kode kita dipenuhi (bool), json_decode(), dan new DateTime() manual. Attribute casting menyelesaikan ini secara deklaratif: kita cukup mendaftarkan tipe tiap kolom di property $casts, dan Eloquent mengonversi nilainya setiap kali dibaca maupun ditulis. Chapter ini membedah cast primitif, cast tanggal berbasis Carbon, cast terenkripsi, sampai method casts() — semuanya dibuktikan lewat tabel posts kita.
Property $casts #
Casting dideklarasikan sebagai array “kolom => tipe” di model. Berikut definisi lengkap untuk Post:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ['user_id', 'judul', 'slug', 'isi', 'views', 'harga', 'published', 'meta'];
protected $casts = [
'views' => 'integer',
'harga' => 'decimal:2',
'published' => 'boolean',
'meta' => 'array',
];
}Tipe yang tersedia jauh lebih banyak dari empat di atas. Tabel berikut merangkum yang paling sering dipakai:
| Cast | Database menyimpan | Model mengembalikan |
|---|---|---|
int / integer | bilangan bulat | int PHP |
real / float / double | bilangan riil | float PHP |
decimal:2 | angka presisi | string "150000.00" dengan dua desimal |
bool / boolean | 0 / 1 | true / false |
string | teks | string PHP |
array / json | string JSON | array PHP |
object | string JSON | stdClass |
collection | string JSON | Illuminate\Support\Collection |
date / datetime | string tanggal | instance Carbon |
datetime:Y-m-d | string tanggal | Carbon yang terserialisasi dengan format kustom |
encrypted | teks tersandi | nilai terdekripsi |
encrypted:array | JSON terenkripsi | array PHP |
Beberapa cast menerima parameter lewat titik dua: decimal:2 menentukan jumlah desimal, datetime:Y-m-d menentukan format serialisasi.
Tanpa Cast vs Dengan Cast #
Mari kita buktikan bedanya dengan membandingkan baris yang sama: sekali lewat query builder mentah, sekali lewat model ber-cast:
$row = DB::table('posts')->where('id', 1)->first();
echo 'RAW views : ', var_export($row->views, true), "\n";
echo 'RAW published: ', var_export($row->published, true), "\n";
echo 'RAW harga : ', var_export($row->harga, true), "\n";
echo 'RAW meta : ', var_export($row->meta, true), "\n";
$post = Post::find(1);
echo 'CAST views : ', var_export($post->views, true), "\n";
echo 'CAST published: ', var_export($post->published, true), "\n";
echo 'CAST harga : ', var_export($post->harga, true), "\n";
echo 'CAST meta : ', var_export($post->meta, true), "\n";Output yang Dihasilkan:
RAW views : 150
RAW published: 1
RAW harga : 150000
RAW meta : '{"sumber":"kursus-laravel","editor":"Budi"}'
CAST views : 150
CAST published: true
CAST harga : '150000.00'
CAST meta : array (
'sumber' => 'kursus-laravel',
'editor' => 'Budi',
)Perhatikan baris views yang tidak berubah: SQLite memang sudah mengembalikan integer, dan cast integer menjamin tipe itu tetap konsisten di semua driver. Sisanya berubah signifikan: angka 1 menjadi true sehingga $post->published aman dipakai di kondisi if, string JSON menjadi array siap diakses lewat $post->meta['sumber'], dan harga berupa string dua desimal — cast decimal memang mengembalikan string berformat, bukan float, agar presisinya terjaga.
Cast Tanggal: Carbon di Balik Layar #
Timestamp created_at dan updated_at otomatis ter-cast datetime meski kita tidak menuliskannya — itulah sebabnya keduanya selalu berupa instance Carbon, library tanggal bawaan Laravel. Dengan Carbon, manipulasi tanggal menjadi fasih:
$row = DB::table('posts')->where('id', 1)->first();
echo 'RAW created_at: ', var_export($row->created_at, true), "\n";
$post = Post::find(1);
echo 'Tipe di model : ', get_class($post->created_at), "\n";
echo 'format() : ', $post->created_at->format('d-m-Y'), "\n";
echo 'isPast() : ', $post->created_at->isPast() ? 'true' : 'false', "\n";
Carbon\Carbon::setLocale('id');
echo 'diffForHumans(): ', $post->created_at->diffForHumans(), "\n";Output yang Dihasilkan:
RAW created_at: '2026-01-10 08:00:00'
Tipe di model : Illuminate\Support\Carbon
format() : 10-01-2026
isPast() : true
diffForHumans(): 5 hari yang laluDi database nilai created_at hanyalah string; di model ia menjadi Carbon dengan method fasih seperti format('d-m-Y'), isPast(), dan diffForHumans(). Dua baris terakhir dijalankan dengan jam dibekukan lewat Carbon::setTestNow('2026-01-15 09:00:00') agar hasil diffForHumans() konsisten, dan Carbon::setLocale('id') membuat frasanya berbahasa Indonesia.
Varian datetime:Y-m-d berperilaku khusus: atribut yang dibaca tetap berupa objek Carbon — hanya serialisasi ke array/JSON yang mengikuti format tersebut. Kita buktikan perilaku ini nanti pada model yang sekaligus memakai cast terenkripsi.
Menulis Nilai Melalui Cast #
Cast bekerja dua arah: nilai yang kita set juga dikonversi sebelum menyentuh database. Kita set string ke kolom integer, float ke kolom decimal, dan array ke kolom JSON:
$post = Post::find(1);
$post->views = '250';
$post->harga = 99000.5;
$post->meta = ['sumber' => 'kursus-laravel', 'editor' => 'Budi', 'revisi' => 2];
$post->save();
$fresh = DB::table('posts')->where('id', 1)->first();
echo 'DB views setelah model set string \'250\': ', var_export($fresh->views, true), "\n";
echo 'DB harga setelah model set 99000.5 : ', var_export($fresh->harga, true), "\n";
echo 'DB meta setelah model set array : ', $fresh->meta, "\n";
echo 'Dibaca ulang harga: ', var_export(Post::find(1)->harga, true), "\n";Output yang Dihasilkan:
DB views setelah model set string '250': 250
DB harga setelah model set 99000.5 : 99000.5
DB meta setelah model set array : {"sumber":"kursus-laravel","editor":"Budi","revisi":2}
Dibaca ulang harga: '99000.50'String '250' tersimpan sebagai integer, dan array otomatis di-json_encode — kita tidak pernah menulis serialisasi itu secara manual. Saat dibaca ulang lewat model, harga kembali sebagai string dua desimal '99000.50': penulisan dan pembacaan simetris. Inilah pasangan alami mutator di chapter sebelumnya: mutator untuk transformasi khusus, cast untuk konversi tipe yang sudah tersedia.
Cast Terenkripsi: encrypted:array #
Data sensitif seperti kredensial pihak ketiga bisa sekaligus dienkripsi saat disimpan dan didekripsi saat dibaca. Perbarui definisi cast model kita — sekaligus tambahkan datetime:Y-m-d pada created_at:
protected $casts = [
'views' => 'integer',
'harga' => 'decimal:2',
'published' => 'boolean',
'meta' => 'encrypted:array',
'created_at' => 'datetime:Y-m-d',
];Kemudian buat post baru yang memuat data rahasia:
$post = Post::create([
'user_id' => 1,
'judul' => 'Data Voucher Rahasia',
'slug' => 'data-voucher-rahasia',
'isi' => 'Contoh cast encrypted:array.',
'views' => 0,
'published' => false,
'meta' => ['kode_voucher' => 'PROMO-2026', 'catatan' => 'data sensitif'],
]);
$raw = DB::table('posts')->where('id', $post->id)->value('meta');
echo '10 karakter pertama di database: ', substr($raw, 0, 10), "...\n";
echo 'Panjang string terenkripsi > 200: ', strlen($raw) > 200 ? 'true' : 'false', "\n";
echo 'Dibaca kembali: ', var_export($post->fresh()->meta, true), "\n";
$fresh = $post->fresh();
echo 'created_at tetap objek Carbon: ', get_class($fresh->created_at), "\n";
echo 'created_at di toArray(): ', var_export($fresh->toArray()['created_at'], true), "\n";Output yang Dihasilkan:
10 karakter pertama di database: eyJpdiI6In...
Panjang string terenkripsi > 200: true
Dibaca kembali: array (
'kode_voucher' => 'PROMO-2026',
'catatan' => 'data sensitif',
)
created_at tetap objek Carbon: Illuminate\Support\Carbon
created_at di toArray(): '2026-01-15'Output ini membuktikan empat fakta. Kolom meta hanya menyimpan teks tersandi berawalan eyJpdiI6 (payload terenkripsi yang di-base64), sementara $post->meta tetap berupa array biasa berkat dekripsi otomatis. Enkripsi memakai APP_KEY, jadi teks tersebut tidak terbaca tanpa kunci yang sama. Terakhir, created_at di model tetap objek Carbon penuh — cast datetime:Y-m-d hanya mengubah bentuknya saat diserialisasi toArray() menjadi '2026-01-15'.
Method casts(): Bentuk Modern #
Sejak Laravel 11 ada cara alternatif mendefinisikan cast: method casts() alih-alih property $casts. Model User bawaan Laravel 13 memakai bentuk ini:
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}Kedua bentuk setara dan boleh dipakai bersama; bila kolom yang sama terdaftar di keduanya, nilai dari method casts() yang menang. Method ini unggul saat definisi cast butuh logika dinamis, misalnya memilih tipe dari konfigurasi aplikasi. Perhatikan juga cast hashed pada baris password: bentuk singkat bawaan untuk menghash otomatis, padanan dari mutator hashing di chapter Accessors dan Mutators
.
Menuju Custom Cast #
Seluruh cast di atas adalah tipe bawaan Eloquent. Untuk transformasi yang belum tersedia — misalnya class value object Money atau Address — kita bisa menulis custom cast: class yang mengimplementasikan kontrak CastsAttributes dengan method get() (database → objek) dan set() (objek → database). Transformasi kedua arah yang boleh berbeda inilah yang menjadikan custom cast asymmetric: proses baca dan tulis tidak harus simetris. Daftar tipe bawaan yang lebih lengkap beserta panduan custom cast tersedia di dokumentasi Eloquent: Mutators
; pembahasan praktiknya menunggu section Advanced Laravel.
Model kini mengonversi nilai otomatis ke tipe yang tepat. Masalah berikutnya bukan lagi tipe, melainkan cara menyajikan ribuan baris kepada pengguna tanpa memuat semuanya dalam satu halaman raksasa — jawabannya pagination, topik chapter berikutnya.