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
php artisan make:cast AddressData
You will now have a file app/Casts/AddressData.php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class AddressData implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
*/
public function get($model, $key, $value, $attributes)
{
return $value;
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return $value;
}
}
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.
public const DEFAULT_DATA = [
'name' => '',
'address1' => '',
'address2' => '',
'city' => '',
'state' => '',
'country' => '',
];
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.
public function get($model, $key, $value, $attributes)
{
if (!is_null($value)) {
return array_merge(self::DEFAULT_DATA, json_decode($value, true));
}
return self::DEFAULT_DATA;
}
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.
// Import class
use Illuminate\Support\Arr;
// Set method
public function set($model, $key, $value, $attributes)
{
return json_encode(Arr::only($value, array_keys(self::DEFAULT_DATA)));
}
Our complete file should look like this:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Arr;
class AddressData implements CastsAttributes
{
public const DEFAULT_DATA = [
'name' => '',
'address1' => '',
'address2' => '',
'city' => '',
'state' => '',
'country' => '',
];
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array
*/
public function get($model, $key, $value, $attributes)
{
if (!is_null($value)) {
return array_merge(self::DEFAULT_DATA, json_decode($value, true));
}
return self::DEFAULT_DATA;
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param array $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return json_encode(Arr::only($value, array_keys(self::DEFAULT_DATA)));
}
}
We now update our User model to add our new casts class that we created.
// Import our Eloquent Casts AddressData Class
use App\Casts\AddressData;
// Update our casts variable
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'address_data' => AddressData::class,
];
You can now safely update and store only the data you need
$user = User::first();
$address = [
'name' => 'Jane Doe',
'address1' => '123 Fake St',
];
$user->address_data = $address;
$user->save();
dd($user->address_data);
// Will output
array:6 [
"name" => "Jane Doe"
"address1" => "123 Fake St"
"address2" => ""
"city" => ""
"state" => ""
"country" => ""
]
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.