Automatically Format Laravel Database Fields with Accessors and Mutators
There’s a really neat feature in Laravel that often gets overlooked because of the feature’s complicated sounding name. We’re talking about accessors and mutators.
What exactly are accessors and mutators?
- Accessors: Format something when retrieving from the database
- Mutators: Format something when saving to the database
One of the most common uses for a mutator is to be sure that a user’s password is always hashed before it gets saved into the database. This ensures that you don’t have to always remember to hash a password wherever you save a user.
The Basics
For our examples, let’s say that we have aUser
Eloquent model. These are the fields on our User:
- first_name
- last_name
- email
- username
- password
- expires_at
- explodes_at
- gets_mad_at
For our most basic of examples, let’s say that we always want to
ensure that when we grab a user, their first name is capitalized. This
is so we can display it to our users and not have to worry about all
lowercase names.Here is the code for when we create a user:
$user = App\User::create([
'first_name' => 'chris',
'last_name' => 'sevilleja',
'email' => 'chris&64;scotch.io',
'password' => 'password',
'expires_at' => Carbon::now()->addMonth(),
'explodes_at' => Carbon::now()->addDay(),
'gets_mad_at' => Carbon::yesterday()
]);
Our user didn’t care to enter in their first name or last name with
the proper punctuation but we still want to show their name as
capitalized. If we save our user to the database with the above, we’ll
need to use an accessor to format the user’s first_name
and last_name
when we retrieve them.
Accessors use getAttribute
An accessor will be defined on our User
model:<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
/**
* Always capitalize the first name when we retrieve it
*/
public function getFirstNameAttribute($value) {
return ucfirst($value);
}
/**
* Always capitalize the last name when we retrieve it
*/
public function getLastNameAttribute($value) {
return ucfirst($value);
}
}
Very easy to do. We just define a getAttribute()
method and make sure that we camel case the attribute name (first_name
turns into getFirstName
).Then we use PHP’s ucfirst function to capitalize the first letter. Easy!
Mutators use setAttribute
Now let’s say we wanted to handle capitalizing a user’s name when we
save them to the database so that our database is uniform in all of its
name fields. Just like how we created the getAttribute
method, we’ll create a setAttribute
method.The process itself will be a little different though.
<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
/**
* Always capitalize the first name when we save it to the database
*/
public function setFirstNameAttribute($value) {
$this->attributes['first_name'] = ucfirst($value);
}
/**
* Always capitalize the last name when we save it to the database
*/
public function setLastNameAttribute($value) {
$this->attributes['last_name'] = ucfirst($value);
}
}
The big difference is we are adding Attribute
to the
naming convention here and instead of returning something (we’re not
getting anything), we’ll directly call the attribute on the model using $this->attributes
.Now whenever we save to the database, we can make sure that these fields are capitalized.
Which do I use?
Now that we’ve seen accessors and mutators in action, which do you use and when? That really all depends on the scenario. Sometimes you’ll need to only use an accessor or mutator; other times you will need to use both.An instance of just using a mutator would be hashing passwords. We’re not going to unhash a password when we retrieve it.
A time we would use both accessor and mutator is for
json_encode
ing and json_decode
ing JSON objects in a Postgres database.Let’s look at some more examples to get the hang of things.
Hashing Passwords
Here’s a really quick example of how we can always ensure that passwords are never saved plain text into our database.<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Hash;
class User extends Model {
/**
* Always capitalize the first name when we save it to the database
*/
public function setPasswordAttribute($value) {
$this->attributes['password'] = Hash::make($value);
}
}
Encoding and Decoding JSON Objects
Let’s say we add a field to ourUser
model called settings
and we store all their settings in a JSON field.
// get the scotchyscotch user
$user = App\User::where('username', 'scotchyscotch')->first();
// give them some settings
$user->settings = [
'notifications' => true,
'location' => 'Las Vegas'
];
Now this wouldn’t save to our database since it’s an array going into a JSON field. We could json_encode
the settings
field but that would be tedious to remember to do it all over our application.We would also have to remember to
json_decode
this settings
field whenever we grabbbed a user.Luckily we can use an accessor and a mutator to ensure this
settings
field is always the way we want it to be.<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
/**
* Always json_decode settings so they are usable
*/
public function getSettingsAttribute($value) {
return json_decode($value);
// you could always make sure you get an array returned also
// return json_decode($value, true);
}
/**
* Always json_encode the settings when saving to the database
*/
public function setSettingsAttribute($value) {
$this->attributes['settings'] = json_encode($value);
}
}
Slug-ifying Slugs
Another quick example is when we are saving an article to a database. We will need to have aslug
for that article.
// the article
$title = 'What Does Water on Mars Mean?';
// the slug
$slug = 'what-does-water-on-mars-mean';
We wouldn’t want to have to have a user enter in the slug every time
they saved a new article. We can use a mutator to create the slug for
us!<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
/**
* Create the slug from the title
*/
public function setSlugAttribute($value) {
// grab the title and slugify it
$this->attributes['slug'] = str_slug($this->title);
}
}
We are calling $this->title
and the Laravel helper method str_slug to create the slug
attribute when we save our model.We are using
$this->title
instead of $this->attributes['title']
because we are not actually setting the title, we just want to get it.Always Get Date Objects
It is much easier to use Carbon, the extension of the native PHPDateTime
class when dealing with dates or times.
// this is much easier
$date = Carbon::now();
$date = $date->addMonth();
// than dealing with this
$date = '2012-01-31 00:00:00';
$date = date('Y-m-d', strtotime('+1 month', $date));
The DateTime
string is not the easiest thing to read or
work with so we usually have to use Carbon to work with our dates.
Laravel handily makes sure that all of our dates are stored in the
correct format.When saving our dates, we are allowed to pass in different dates like so:
// get the awesomedude user
$user = App\User::where('username', 'awesomedude')->first();
// all are valid ways to set the date
$user->expires_at = '2015-09-30 23:01:34';
$user->expires_at = '2015-06-26';
$user->expires_at = Carbon::now();
$user->expires_at = Carbon::tomorrow();
$user->expires_at = 1443765240; // unix timestamp
// save the user
$user->save();
How does Laravel know which fields are dates though? That can be defined on the Eloquent Model using the $dates
property. By default, the created_at
and updated_at
fields are automatically mutated into dates.Unlike the above accessors/mutators where we define a specific method, we can override the default dates and set more date fields to be mutated by overriding the
$dates
property on our model like so:<?php
// app/User.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at',
'expires_at',
'gets_mad_at'
];
}
All of the fields above will be set to the proper date type now when saving our model.Inversely, all of the
$dates
fields will be casted into Carbon instances upon retrieval.
// get awesomedudette
$user = App\User::where('username', 'awesomedudette')->first();
// find an hour before the time this user explodes at
$explodeWarning = $user->explodes_at->subHour();