Jose Jimenez
Jose Jimenez
Software Architect & Developer
> >

Define your own Laravel 7 Custom Eloquent Casts

Published in Laravel on May 11, 2020

As of version 7, we can now declare our own Eloquent cast types. Let's assume we have a database JSON column that stores a user's address, by using Eloquent cast types you can always ensure that column retrieves and stores your data in that strict format.

Let's assume we have a JSON column called "address_data" on your users table and we wanted to store the minimum amount of data, but in a way to standarize it for future use.

Let's start by making our casts class

1php artisan make:cast AddressData

You will now have a file app/Casts/AddressData.php

1namespace App\Casts;
 2
3use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
 4
5class AddressData implements CastsAttributes
 6{
7 /**
8 * Cast the given value.
9 *
10 * @param \Illuminate\Database\Eloquent\Model $model
11 * @param string $key
12 * @param mixed $value
13 * @param array $attributes
14 * @return array
15 */
16 public function get($model, $key, $value, $attributes)
17 {
18 return $value;
19 }
20
21 /**
22 * Prepare the given value for storage.
23 *
24 * @param \Illuminate\Database\Eloquent\Model $model
25 * @param string $key
26 * @param array $value
27 * @param array $attributes
28 * @return string
29 */
30 public function set($model, $key, $value, $attributes)
31 {
32 return $value;
33 }
34}

Let's start by adding a DEFAULT_DATA constant variable, I do this to more easily see the structure of my column, and to work more easily with it in the future.

1public const DEFAULT_DATA = [
2 'name' => '',
3 'address1' => '',
4 'address2' => '',
5 'city' => '',
6 'state' => '',
7 'country' => '',
8];

Next let's update our get() method, this will grab the data from the database, and merge it with our default data, allowing us return a standard format each and every time.

1public function get($model, $key, $value, $attributes)
2{
3 if (!is_null($value)) {
4 return array_merge(self::DEFAULT_DATA, json_decode($value, true));
5 }
6 return self::DEFAULT_DATA;
7}

Lastly let's update our set() method, to easily store only the data defined in our DEFAULT data, we will use the array only helper, to store values defined by our default data, this will ensure only that data exists in our system.

1// Import class
2use Illuminate\Support\Arr;
3
4// Set method
5public function set($model, $key, $value, $attributes)
6{
7 return json_encode(Arr::only($value, array_keys(self::DEFAULT_DATA)));
8}

Our complete file should look like this:

 1<?php
 2
3namespace App\Casts;
 4
5use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6use Illuminate\Support\Arr;
 7
8class AddressData implements CastsAttributes
 9{
10 public const DEFAULT_DATA = [
11 'name' => '',
12 'address1' => '',
13 'address2' => '',
14 'city' => '',
15 'state' => '',
16 'country' => '',
17 ];
18
19 /**
20 * Cast the given value.
21 *
22 * @param \Illuminate\Database\Eloquent\Model $model
23 * @param string $key
24 * @param mixed $value
25 * @param array $attributes
26 * @return array
27 */
28 public function get($model, $key, $value, $attributes)
29 {
30 if (!is_null($value)) {
31 return array_merge(self::DEFAULT_DATA, json_decode($value, true));
32 }
33 return self::DEFAULT_DATA;
34 }
35
36 /**
37 * Prepare the given value for storage.
38 *
39 * @param \Illuminate\Database\Eloquent\Model $model
40 * @param string $key
41 * @param array $value
42 * @param array $attributes
43 * @return string
44 */
45 public function set($model, $key, $value, $attributes)
46 {
47 return json_encode(Arr::only($value, array_keys(self::DEFAULT_DATA)));
48 }
49}

We now update our User model to add our new casts class that we created.

1// Import our Eloquent Casts AddressData Class
 2
3use App\Casts\AddressData;
 4
5// Update our casts variable
 6
 7/**
8 * The attributes that should be cast to native types.
9 *
10 * @var array
11 */
12protected $casts = [
13 'address_data' => AddressData::class,
14];

You can now safely update and store only the data you need

1$user = User::first();
 2
3$address = [
4 'name' => 'Jane Doe',
5 'address1' => '123 Fake St',
 6];
 7
8$user->address_data = $address;
 9$user->save();
10
11dd($user->address_data);
12
13// Will output
14
15array:6 [
16 "name" => "Jane Doe"
17 "address1" => "123 Fake St"
18 "address2" => ""
19 "city" => ""
20 "state" => ""
21 "country" => ""
22]

As you can see in the above example, we only stored 2 pieces of information, name and address1, however when we retrieved through ->address_data our casts class was able to easily format and return the data in the expected format that we needed, and only with the data that was filled in.