How to set default values when casting NULL attributes in Laravel
Note: This guide was written for Laravel 5, so the information may be outdated.
One of the many useful features of Eloquent is the ability to define accessors and mutators. Mutators can help you if there’s something you’d like to do with a value (like hashing a password) every time it gets set on your model. Accessors are for doing something every time you retrieve an attribute (like converting markdown to HTML). You can even use an accessor to dynamically create additional properties on your model (like a full name that concatenates first and last names).
Those are great features, but we also have attribute casting so we can convert attributes to common data types without the need for an accessor. These include integer, real, float, double, string, boolean, object, array, collection, date, datetime, and timestamp.
One problem you might come across with casting attributes is that there’s no way to specify a default value when an attribute is null. If you look at the source you can see it checks if the value is null before doing the casting. So what can we do if we want a default? Well, like with any problem there’s more than one way to solve this.
The first option I’d recommend is to just use an accessor like so:
/**
* Accessor for the options attribute.
* Defaults to an empty array.
*/
public function getOptionsAttribute($value)
{
if (is_null($value)) {
$value = [];
}
return $value;
}
While this option is quick and easy to implement, it can get repetitive and add bloat to your models very quickly if you need to use it for a lot of attributes.
If you do end up with tons of accessors just to handle null values when casting attributes, you might want to consider extending Eloquent’s Model class and overriding the castAttribute()
method like so:
<?php
namespace App;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Database\Eloquent\Model as EloquentModel;
abstract class Model extends EloquentModel
{
/**
* Cast an attribute to a native PHP type.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function castAttribute($key, $value)
{
if (! is_null($value)) {
return parent::castAttribute($key, $value);
}
switch ($this->getCastType($key)) {
case 'int':
case 'integer':
return (int) 0;
case 'real':
case 'float':
case 'double':
return (float) 0;
case 'string':
return '';
case 'bool':
case 'boolean':
return false;
case 'object':
case 'array':
case 'json':
return [];
case 'collection':
return new BaseCollection();
case 'date':
return $this->asDate('0000-00-00');
case 'datetime':
return $this->asDateTime('0000-00-00');
case 'timestamp':
return $this->asTimestamp('0000-00-00');
default:
return $value;
}
}
}
For any values that aren’t null, we just use the parent method to cast the value as expected. If it is null, we’ll use a switch to return default values for the different types. If you want to change the defaults or leave certain types as null, just change the method accordingly.
Now we need to make sure instead of having our models extend the standard Illuminate\Database\Eloquent\Model
class, we have them extend our own App\Model
class. We could also put the method into a trait and use it only on the models where we really need it, but you might find some utility in having your own Model
class where you can add other custom behaviors to all of your models.
Hopefully I’ve at least given you some ideas even if your implementation is different.