Properti $casts Laravel adalah salah satu kemudahan tersembunyi yang membentuk cara Anda menangani data tanpa terlalu mencolok. Masukkan tipe bawaan seperti boolean atau datetime ke dalam array, dan Eloquent secara otomatis mengubah string database mentah menjadi sesuatu yang lebih ramah sebelum nilainya mencapai controller atau view Anda. Ini menjaga kode Anda tetap rapi. Namun, saat Anda mencoba memanggil helper sendiri secara langsung di dalam array tersebut, framework akan menolaknya. Menulis sesuatu seperti 'username' => 'encrypt_data()' tidak akan berhasil. Laravel mengharapkan kata kunci cast bawaan atau sebuah class yang mengimplementasikan interface CastsAttributes. Persyaratan tersebut ada karena lapisan casting berjalan jauh di dalam siklus hidrasi dan serialisasi Eloquent; ia membutuhkan objek yang dapat diprediksi dengan kontrak yang jelas, alih-alih string fungsi sembarang yang mungkin tidak ada saat runtime.

Mengapa Fungsi Helper Gagal dalam Array $casts

Saat Eloquent memuat sebuah model, ia memindai array $casts untuk memutuskan cara mentransformasi setiap kolom. Framework mengenali primitif tertentu—string, integer, array, encrypted, dan sebagainya—serta mengenali nama class fully-qualified yang mengimplementasikan CastsAttributes. Ia tidak mengevaluasi string tersebut sebagai kode. Jadi 'encrypt_data()' dianggap sebagai tipe cast yang tidak dikenal, yang memicu error. Bahkan jika Laravel mengevaluasinya, tidak akan ada cara yang andal untuk meneruskan instance model, kunci atribut, nilai saat ini, dan atribut sekitarnya ke dalam fungsi Anda dengan urutan yang benar. Interface tersebut ada justru untuk menstandarisasi handshake antara logika kustom Anda dan internal Eloquent.

Anda memiliki dua cara ampuh untuk menjembatani celah ini. Satu cara mengutamakan penggunaan kembali jangka panjang. Cara lainnya mengutamakan kecepatan saat Anda hanya membutuhkan perbaikan cepat di dalam satu model saja.

Metode 1: Menulis Class Cast Kustom

Jika transformasi yang sama berlaku untuk beberapa field atau tersebar di beberapa model, class cast khusus adalah pilihan yang lebih bersih. Ia berada di filenya sendiri, dapat diuji unit secara terisolasi, dan menjaga model Anda bebas dari boilerplate yang berulang.

Mulailah dengan membuat app/Casts/CustomEncrypt.php. Namespace harus sesuai dengan pengaturan autoloading Anda, biasanya App\Casts. Class tersebut harus mengimplementasikan Illuminate\Contracts\Database\Eloquent\CastsAttributes, yang mengharuskan Anda mendefinisikan dua metode: get dan set.

<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class CustomEncrypt implements CastsAttributes
{
    public function get(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return decrypt_data($value);
    }

    public function set(Model $model, string $key, mixed $value, array $attributes): mixed
    {
        return encrypt_data($value);
    }
}

Signature metode tersebut sangat penting. Eloquent meneruskan empat argumen ke dalam setiap metode. $model adalah instance yang sedang diisi atau disimpan, yang memungkinkan Anda memeriksa field lain jika logika Anda bergantung padanya. $key adalah nama kolom yang sedang di-cast. $value adalah string mentah atau null yang datang dari database pada get, atau nilai yang diberikan pengguna pada set. $attributes adalah seluruh array mentah dari kolom-kolom untuk baris tersebut. Anda tidak perlu menggunakan keempatnya setiap saat, tetapi interface tersebut mewajibkannya.

Dalam metode get di atas, decrypt_data($value) berjalan setelah Eloquent mengambil baris dari database dan sebelum nilainya masuk ke model Anda. Dalam metode set, encrypt_data($value) berjalan sebelum INSERT atau UPDATE, memastikan database tidak pernah melihat plaintext.

Untuk menghubungkannya, referensikan class tersebut di dalam array $casts model Anda:

protected $casts = [
    'username' => CustomEncrypt::class,
    'password' => CustomEncrypt::class,
];

Karena Anda menggunakan konstanta class, autoloader akan menangani sisanya. Jika aplikasi Anda nantinya perlu mengganti skema enkripsi, Anda cukup mengedit satu file, dan setiap field yang dipetakan akan berubah perilakunya secara instan. Sentralisasi tersebut sulit dikalahkan saat Anda mengelola data sensitif di banyak tabel.

Metode 2: Gunakan Accessor dan Mutator

Terkadang Anda tidak menginginkan file baru untuk transformasi yang hanya penting di satu tempat. Class Attribute milik Laravel memungkinkan Anda mendefinisikan logika get dan set secara langsung pada model menggunakan sintaks closure PHP 8+.

use Illuminate\Database\Eloquent\Casts\Attribute;

protected function username(): Attribute
{
    return Attribute::make(
        get: fn ($value) => decrypt_data($value),
        set: fn ($value) => encrypt_data($value),
    );
}

Di sini, nama metode harus sesuai dengan kolom atau atribut yang Anda targetkan. Saat Eloquent membaca username dari database, ia meneruskan nilai mentah melalui closure get. Saat Anda menetapkan nilai baru ke properti username model, closure set akan mengenkripsinya sebelum Eloquent membangun query.

Pendekatan ini sangat berguna dalam prototipe atau model lama di mana direktori penuh berisi class cast terasa berlebihan. Kekurangannya adalah pengulangan. Jika nantinya Anda memutuskan bahwa email, phone, dan backup_code memerlukan perlakuan yang sama, Anda akan berakhir dengan menyalin closure tersebut ke berbagai metode atau model. "Noise" tersebut akan menumpuk. Hal ini juga mencegah Anda menggunakan kembali logika tersebut dalam unit test tanpa harus menjalankan (booting) seluruh model.

Memilih di Antara Keduanya

Gunakan class cast kustom saat Anda peduli dengan penggunaan kembali (reuse), pengujian, dan menjaga model tetap ramping. Ini memberi sinyal kepada pengembang lain bahwa transformasi ini adalah konsep utama (first-class concept) dalam aplikasi Anda, bukan sekadar solusi sementara (one-off hack).

Gunakan accessor saat logikanya benar-benar terlokalisasi, bersifat eksperimental, atau kemungkinan besar tidak akan bertahan lebih lama dari sprint saat ini. Ini memungkinkan Anda bergerak cepat tanpa menyebarkan banyak file kecil di seluruh codebase. Hanya saja, bersiaplah untuk melakukan refactor menjadi class cast setelah pola yang sama muncul untuk kedua atau ketiga kalinya.

Detail Praktis yang Perlu Diingat

Custom cast sangat kuat, tetapi mereka memperkenalkan perilaku yang dapat mengejutkan Anda jika Anda tidak waspada. Pertama, ingatlah bahwa get menerima apa pun yang dikembalikan oleh database, termasuk null. Jika decrypt_data tidak dapat menangani input null, berikan proteksi:

public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
    return is_null($value) ? null : decrypt_data($value);
}

Kedua, cast berjalan selama serialisasi array dan JSON. Saat Anda mengembalikan model sebagai API resource atau memanggil toArray(), logika get pada cast tetap berlaku. Biasanya itulah yang Anda inginkan, tetapi perlu diingat jika Anda mengekspos nilai yang telah didekripsi dan perlu menambahkan lapisan kontrol visibilitas di atasnya.

Ketiga, dan yang paling penting, casting terjadi setelah pengambilan data (retrieval). Anda tidak dapat melakukan query terhadap nilai yang telah ditransformasi. Query seperti User::where('username', 'john_doe')->first() mengirimkan string john_doe langsung ke database. Query tersebut tidak pernah melewati logika dekripsi Anda. Jika kolom tersebut dienkripsi saat disimpan (encrypted at rest), query tersebut tidak akan menemukan apa pun kecuali Anda mencari terhadap ciphertext terenkripsi itu sendiri. Rencanakan pola akses database Anda dengan tepat, karena cast bukanlah pengganti fungsi database asli atau kolom plaintext yang terindeks.

Class custom cast juga merupakan tempat yang tepat untuk potongan konfigurasi kecil. Jika Anda memerlukan argumen konstruktor—mungkin untuk mengirimkan mode cipher atau string format—Laravel mendukungnya melalui array $casts menggunakan ekspresi seperti 'field' => CustomEncrypt::class . ':arg', meskipun itu selangkah lebih maju dari pengaturan dasar yang dijelaskan di sini.

Kesimpulan Utama

Pembatasan terhadap penggunaan fungsi helper secara langsung di dalam $casts bukanlah birokrasi yang sembarangan. Hal ini mendorong Anda menuju kode yang eksplisit, dapat diuji, dan dapat digunakan kembali. Class custom cast mengubah logika inline yang tersebar menjadi komponen andal yang dapat Anda bagikan ke berbagai model tanpa duplikasi. Accessor membiarkan pintu tetap terbuka untuk perbaikan cepat dan terlokalisasi ketika penggunaan file terpisah terasa seperti formalitas yang berlebihan. Kuasai keduanya, pilih berdasarkan cakupan masalah, dan lapisan Eloquent Anda akan tetap mudah dibaca lama setelah aplikasi berkembang.