Laravel Model Casts: JSON, Enums & Encryption
When working with relational databases in Laravel, data stored in table columns often needs transformation before you can use it in PHP. For instance, JSON columns need to be deserialized into PHP arrays or collections, integer or string status codes are better represented as PHP 8 Enums, and sensitive user data (like API tokens or identity numbers) must be stored encrypted at rest.
Without model casting, you would have to manually run json_decode(), Crypt::encryptString(), or enum initialization every time you access or persist data.
In this article, we will learn how to simplify your data layer using Laravel Model Casts—specifically focusing on working with JSON columns, PHP Enums, and Encrypted data.
What are Eloquent Model Casts?
Eloquent attribute casting provides a convenient mechanism for converting attributes to common data types when retrieving or setting values on model instances.
In modern Laravel, you can define casts using the traditional $casts property or the modern casts() method on your Eloquent model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_active' => 'boolean',
'created_at' => 'datetime',
];
}
}
The casts() method is supported in current Laravel versions and allows you to define casts directly using class-based syntax and helper methods.
1. Working with JSON Data in Laravel Models
Databases like MySQL and PostgreSQL support native JSON column types. In Laravel, you can automatically convert stored JSON strings to native PHP arrays, collections, or mutable objects.
Casting to Array or Collection
Suppose you have a settings or preferences column in your database table defined as $table->json('preferences') in your migration:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = [
'name',
'email',
'preferences',
];
protected function casts(): array
{
return [
'preferences' => 'array', // Automatically encodes/decodes JSON
];
}
}
Now, whenever you interact with the preferences column, you don't need to call json_encode() or json_decode() manually:
// Creating a user with array data
$user = User::create([
'name' => 'Alex Johnson',
'email' => 'alex@example.com',
'preferences' => [
'theme' => 'dark',
'notifications' => true,
'language' => 'en',
],
]);
// Accessing the casted attribute returns a native PHP array
echo $user->preferences['theme']; // Output: dark
Using AsArrayObject for In-Place Mutation
When using standard 'array' casting, updating a nested key like $user->preferences['theme'] = 'light' might not mark the model attribute as dirty unless you reassign the entire array. To allow in-place mutations, Laravel provides the AsArrayObject cast:
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected function casts(): array
{
return [
'preferences' => AsArrayObject::class,
];
}
}
With AsArrayObject, you can mutate nested properties directly and call save():
$user = User::find(1);
// Direct property mutation
$user->preferences['theme'] = 'system';
$user->save(); // Laravel recognizes the change and runs the UPDATE query
2. Working with PHP Enums in Laravel Models
Using raw strings or magic integers for statuses like pending, completed, or failed often leads to typos and validation bugs. PHP 8 Backed Enums allow you to define strictly typed constants, and Laravel provides native support for casting columns to Enums.
Step 1: Define the Backed Enum
Create a backed enum file inside app/Enums/OrderStatus.php:
namespace App\Enums;
enum OrderStatus: string
{
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Cancelled = 'cancelled';
public function label(): string
{
return match ($this) {
self::Pending => 'Order Pending',
self::Processing => 'Processing Order',
self::Completed => 'Completed Successfully',
self::Cancelled => 'Order Cancelled',
};
}
}
Step 2: Add Enum Casting to Your Model
In your Order model, specify the enum class in the casts() method:
namespace App\Models;
use App\Enums\OrderStatus;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = [
'order_number',
'status',
'total_amount',
];
protected function casts(): array
{
return [
'status' => OrderStatus::class,
];
}
}
Step 3: Using the Enum in Application Logic
use App\Enums\OrderStatus;
use App\Models\Order;
// Saving with an Enum instance
$order = Order::create([
'order_number' => 'ORD-9081',
'status' => OrderStatus::Pending,
'total_amount' => 120.50,
]);
// Type-safe checking and helper methods
if ($order->status === OrderStatus::Pending) {
echo $order->status->label(); // "Order Pending"
}
If anyone attempts to pass an invalid status string that isn't defined on OrderStatus, PHP will throw a ValueError, protecting your database from invalid state data.
3. Working with Encrypted Data in Laravel Models
When storing sensitive records like API secrets, bank account numbers, or national identification codes, standard hashing (like bcrypt) is not suitable because you need to read the original decrypted value back. However, storing them in plain text is a security liability.
Laravel provides built-in encrypted casts that encrypt values before saving them to the database and automatically decrypt them when accessed.
Basic Encrypted Casting
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class BankAccount extends Model
{
protected $fillable = [
'user_id',
'account_number',
'routing_number',
];
protected function casts(): array
{
return [
'account_number' => 'encrypted',
'routing_number' => 'encrypted',
];
}
}
When saved, Laravel uses your application's APP_KEY via the Illuminate\Support\Facades\Crypt service to encrypt the data with AES-256-CBC.
$account = BankAccount::create([
'user_id' => 1,
'account_number' => '987654321012',
'routing_number' => '123456789',
]);
// In the database: column holds a long encrypted payload string like "eyJpdiI6..."
// In your PHP code: access the decrypted value seamlessly
echo $account->account_number; // Output: 987654321012
Encrypted JSON & Arrays
If you need to encrypt complex JSON structures or arrays (such as third-party payment tokens or webhook payload secrets), you can use encrypted:array, encrypted:collection, or encrypted:object:
protected function casts(): array
{
return [
'api_credentials' => 'encrypted:array',
];
}
Important Things to Remember
- Column Length for Encrypted Fields: Encrypted payloads produce long strings containing an IV, MAC, and encrypted ciphertext. Always use
$table->text()or$table->longText()in your database migrations instead of$table->string().
- Querying Encrypted Columns: Because Laravel generates a unique initialization vector (IV) on every encryption operation, two identical plaintext values will produce completely different encrypted ciphertexts. As a result, you cannot run direct SQL queries like
BankAccount::where('account_number', $number)->first(). If you need searchability, consider using blind indexing or hashing separate search tokens.
- APP_KEY Dependency: Encrypted attributes rely directly on your
APP_KEYin.env. If your application key changes, you will not be able to decrypt previously stored records without the old key.
Conclusion:
Laravel's Eloquent casts make working with modern data structures effortless. By configuring casts for JSON structures, PHP Enums, and encrypted columns, you eliminate repetitive serialization code, enforce strict type safety across your codebase, and keep your application secure.
Thank you for reading this article 😊
For any query, do not hesitate to comment 💬
-compressed.jpg)