php
46 lines · 7 steps
How a custom Eloquent cast wraps an Address in Laravel
A Laravel custom cast translates a JSON database column into an Address value object and back.
Explained by
highlit
1<?php
2
3namespace App\Casts;
4
5use App\ValueObjects\Address;
6use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
7use Illuminate\Database\Eloquent\Model;
8
9class AddressCast implements CastsAttributes
10{
11 public function get(Model $model, string $key, mixed $value, array $attributes): ?Address
12 {
13 if ($value === null) {
14 return null;
15 }
16
17 $data = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
18
19 return new Address(
20 street: $data['street'],
21 city: $data['city'],
22 postalCode: $data['postal_code'],
23 country: $data['country'],
24 );
25 }
26
27 public function set(Model $model, string $key, mixed $value, array $attributes): array
28 {
29 if ($value === null) {
30 return [$key => null];
31 }
32
33 if (! $value instanceof Address) {
34 throw new \InvalidArgumentException('The given value is not an Address instance.');
35 }
36
37 return [
38 $key => json_encode([
39 'street' => $value->street,
40 'city' => $value->city,
41 'postal_code' => $value->postalCode,
42 'country' => $value->country,
43 ], JSON_THROW_ON_ERROR),
44 ];
45 }
46}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Custom casts keep serialization logic in one place instead of scattering json_decode across your models.
- 2Casting to an immutable value object gives model attributes real types and behavior rather than raw arrays.
- 3Validating the incoming type on set catches misuse at the boundary before bad data reaches the database.
Related explainers
php
final class UserActivityStream { public function __construct( private readonly \PDO $db,
Streaming user activity with PHP generators
generators
pagination
lazy-evaluation
Intermediate
8 steps
javascript
export function buildPaginationUrl(baseUrl, { page, perPage, filters = {}, sort } = {}) { const url = new URL(baseUrl); const params = url.searchParams;
Building and parsing pagination URLs
url-parsing
query-string
pagination
Intermediate
8 steps
rust
use axum::{ extract::Path, http::{header::ACCEPT, HeaderMap, StatusCode}, response::{Html, IntoResponse, Json, Response},
Content negotiation in an Axum handler
content-negotiation
http-headers
serialization
Intermediate
8 steps
php
use Illuminate\Support\Facades\Route; use App\Http\Controllers\Admin\DashboardController; use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\ReportController;
How nested route groups compose in Laravel
routing
middleware
authorization
Intermediate
8 steps
php
final class TokenBucketLimiter { private float $tokens; private float $lastRefill;
How a token bucket rate limiter works
rate-limiting
token-bucket
throttling
Intermediate
7 steps
ruby
module Api module V2 class ArticlesController < Api::BaseController before_action :set_article, only: %i[show update destroy]
Building a versioned JSON API controller in Rails
rest-api
serialization
pagination
Intermediate
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-a-custom-eloquent-cast-wraps-an-address-in-laravel-explained-php-deba/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.