0

Cast — automatic type transformation when reading or writing a model attribute

Beginner5 min read·eng-14-011
laravel-src

Concept

Cast — a declarative instruction telling Eloquent how to automatically convert an attribute's raw database value to a PHP type (and back).

The problem casts solve: Databases store everything as strings or integers. A boolean stored as 1 in MySQL should be true in PHP. A JSON string in a TEXT column should be an array. Without casts, you'd manually convert every time you read or write — tedious and error-prone.

Built-in cast types:

  • 'integer' / 'int': String → int.
  • 'float' / 'double' / 'real': String → float.
  • 'boolean' / 'bool': '1'/'0'true/false.
  • 'string': Cast to string.
  • 'array' / 'json': JSON string ↔ PHP array. Serializes on save, deserializes on read.
  • 'collection': JSON string ↔ Laravel Collection.
  • 'datetime' / 'date' / 'timestamp': String → Carbon instance (with optional format).
  • 'decimal:2': Float with 2 decimal places.
  • 'encrypted': Encrypted at rest, decrypted on read. Uses Laravel's encryption.
  • 'hashed': Auto-hashes on assignment (PHP 8.1+). Like a built-in password mutator.

Custom casts: Implement \Illuminate\Contracts\Database\Eloquent\CastsAttributes — define get() and set() methods. Cast a column to a Value Object (e.g., Money, Email).

Enum casts (PHP 8.1+): 'status' => OrderStatus::class — stores enum ->value in DB, returns enum instance on read.

Difference from accessor/mutator: Casts are declarative and type-focused. Accessors are imperative and can contain any logic.

Code Example

php
<?php
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $casts = [
        'active'         => 'boolean',      // DB: 1/0 → PHP: true/false
        'age'            => 'integer',       // DB: '25' → PHP: 25
        'score'          => 'float',
        'settings'       => 'array',         // DB: JSON string ↔ PHP array
        'preferences'    => 'collection',    // DB: JSON string ↔ Collection
        'created_at'     => 'datetime',      // DB: timestamp string → Carbon
        'birthday'       => 'date',          // DB: '1990-01-15' → Carbon::date
        'price'          => 'decimal:2',     // DB: float → formatted decimal
        'api_token'      => 'encrypted',     // auto-encrypt in DB, decrypt on read
    ];
}

// Usage — automatic type conversion
$user          = User::find(1);
$user->active;   // true (not '1')
$user->age;      // 25 (not '25')
$user->settings; // ['theme' => 'dark', 'lang' => 'en'] (not JSON string)
$user->created_at; // Carbon instance — $user->created_at->diffForHumans()

$user->settings = ['theme' => 'light']; // auto-serialized to JSON on save
$user->save();

// ENUM CAST (PHP 8.1+)
enum OrderStatus: string
{
    case Pending   = 'pending';
    case Shipped   = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';
}

class Order extends Model
{
    protected $casts = [
        'status' => OrderStatus::class, // DB: 'pending' → PHP: OrderStatus::Pending
    ];
}

$order = Order::find(1);
$order->status;                            // OrderStatus::Pending (enum instance)
$order->status === OrderStatus::Pending;   // true
$order->status->value;                     // 'pending' (string value)
$order->status = OrderStatus::Shipped;    // auto-saves 'shipped' in DB

// CUSTOM CAST — cast to a Value Object
class MoneyCast implements \Illuminate\Contracts\Database\Eloquent\CastsAttributes
{
    public function get($model, string $key, mixed $value, array $attributes): Money
    {
        return new Money((int) $value, $attributes['currency'] ?? 'USD');
    }

    public function set($model, string $key, mixed $value, array $attributes): int
    {
        if (!$value instanceof Money) throw new \InvalidArgumentException('Expected Money object');
        return $value->amount;
    }
}

class Product extends Model
{
    protected $casts = [
        'price' => MoneyCast::class, // DB: integer (cents) ↔ PHP: Money object
    ];
}

$product = Product::find(1);
$product->price;              // Money(1999, 'USD')
$product->price->amount;      // 1999 (cents)
$product->price = new Money(2499, 'USD');
$product->save(); // stores 2499 in DB