Black Friday: coupon FRIDAY24 for 40% off Yearly/Lifetime membership! Read more here
Courses

Laravel 11 Eloquent: Expert Level

Saving a Model: $fillable or $guarded?

In the properties of the Eloquent Models, you may find a $fillable array. By default, in the User Model, there are three fillable fields.

class User extends Authenticatable
{
use HasFactory, Notifiable;
 
protected $fillable = [
'name',
'email',
'password',
];
 
// ...
}

So, what are fillables, how do they work, and what are the alternatives?


Practical Example

These fields can be provided when creating a new object with Eloquent. What does it mean?

I have a temporary Route with a Controller.

routes/web.php:

Route::get('/', [\App\Http\Controllers\HomeController::class, 'index']);

In the Controller, I have two ways to create a user. The first way is the create method used on the Model and providing values as an array. The second way is to make the object manually, assign the value to each property, and then hit save.

app/Http/Controllers/HomeController.php:

use App\Models\User;
 
class HomeController extends Controller
{
public function index()
{
// First method
User::create([
'name' => fake()->name(),
'email' => fake()->email(),
'password' => bcrypt('password'),
'email_verified_at' => now(),
]);
 
// Second method
$user = new User();
$user->name = fake()->name();
$user->email = fake()->email();
$user->password = bcrypt('password');
$user->email_verified_at = now();
$user->save();
}
}

Probably, most people like the first method more. Because it is shorter and you don't need to repeat users. Now, let's visit the home page and look at the difference between these two methods, which are related to fillables. Let's check the DB for the users table.

Two users have been created, but do you see the difference? For the first user, the email_verified_at field is null. But, when creating a user in both methods, the email_verified_at is provided. The difference is that this field email_verified_at isn't added to the $fillable array.

So, if you want to create records using the first method, by using the create method on the Model, make sure all the fields are in the $fillable array.

The only exceptions are the id and created_at/updated_at timestamps, which are automatically in the fillables, internally from the framework.


Eloquent, Please Be Strict

The worst part of the case above is that Eloquent didn't throw any errors. It just didn't save the field.

But you can tell Eloquent to be stricter with that in the AppServiceProvider. However, that strictness should only be present in a local environment.

app/Providers/AppServiceProvider.php:

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Model;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
Model::shouldBeStrict(! app()->isProduction());
}
}

If we try to rerun the home page, we will get a MassAssignmentException error.


Alternative: $guarded

There is an alternative syntax if you want to avoid adding $fillable fields for every Model. You can provide the opposite of fillables, called $guarded.

In the $guarded array, you can provide an array of fields that will NOT be fillable. So, typically, people leave it as an empty array.

app/Models/User.php:

class User extends Authenticatable
{
use HasFactory, Notifiable;
 
protected $fillable = [
'name',
'email',
'password',
];
 
protected $guarded = [];
 
// ...
}

Now, after trying to visit the home page, we see no errors. Let's check the DB.

Two new users have been created, and both have the email_verified_at fields filled.

Another place where you can define the non-fillable fields is unguard() method in the AppServiceProvider. You don't need to add the $guarded property in every Model by providing unguard() in the Provider.

app/Providers/AppServiceProvider.php:

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Model;
 
class AppServiceProvider extends ServiceProvider
{
// ...
 
public function boot(): void
{
Model::shouldBeStrict(! app()->isProduction());
Model::unguard();
}
}