Define your own Laravel 7 Custom Eloquent Casts
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 $model11 * @param string $key12 * @param mixed $value13 * @param array $attributes14 * @return array15 */16 public function get($model, $key, $value, $attributes)17 {18 return $value;19 }2021 /**22 * Prepare the given value for storage.23 *24 * @param \Illuminate\Database\Eloquent\Model $model25 * @param string $key26 * @param array $value27 * @param array $attributes28 * @return string29 */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 class2use Illuminate\Support\Arr;34// Set method5public 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 ];1819 /**20 * Cast the given value.21 *22 * @param \Illuminate\Database\Eloquent\Model $model23 * @param string $key24 * @param mixed $value25 * @param array $attributes26 * @return array27 */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 }3536 /**37 * Prepare the given value for storage.38 *39 * @param \Illuminate\Database\Eloquent\Model $model40 * @param string $key41 * @param array $value42 * @param array $attributes43 * @return string44 */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 array11 */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();1011dd($user->address_data);1213// Will output1415array: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.