Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

TheFox's avatar

Model Mass Assignment

Hi,

I know that passing a raw array of user controlled input into a save or update mothod is always a bad idea. For example:

Post::where('id', $id)->update(Input::all());

But while there is a $guarded variable on the Model class why are the fields in the $guarded array still updated when I use the code above?

For example my Post class has this:

protected $guarded = array('id', 'user_id');

I don't have a user_id input field in the edit form, but using the Chrome Developer Tools and adding a single input field on the page with name="user_id" isn't that hard.

So which additional benefit has the $guarded, the $fillable and the $hidden field?

0 likes
42 replies
mstnorris's avatar

Firstly as per the docs on Mass Assignment

Note: When using guarded, you should still never pass Input::get() or any raw array of user controlled input into a save or update method, as any column that is not guarded may be updated.

I don't know why this is, but as the docs say you should still never pass Input::get() or any raw array of user controlled input into a save or update method

Secondly:

$guarded = fields you don't want to be mass assigned, it is the opposite to $fillable, it is bettie to use one or the other.

$fillable = fields that can be mass assigned

$hidden = fields that aren't returned when you dd($model)

TheFox's avatar

Yes, I read that on the Eloquent ORM help page too, but I don't know what it means. The $guarded variable doesn't prodect the input or update method from invalid user input.

mstnorris's avatar

@TheFox

The guarded attributes prevent those table fields being changed with mass assignment only. If you define a specific array (which is not Input::all()), than they will get set. As Zwacky said, using ::create(Input::all()) is awesomely convenient, but also dangerous.. That's why the guarded attribute exists. If what you actually want is a completely unwritable field, than maybe you could try playing with different MySql users and granting them different access on specific columns, or even better, use model events

taken from the Laravel.io Forum

by the looks of things, if you were to use $fillable instead, then that would work, as you can use Input::all() and the $fillable property will take what it can and insert that into the database.

TheFox's avatar

That's why the guarded attribute exists.

But I can assign an attribut that's in $guarded. I don't see the difference.

I tested it. First I set the user_id into $fillable and than I set it to $guarded. And it has always been updated. Maybe you can show me an example code.

mstnorris's avatar

@TheFox is that still using your Input::all()?

If so, can you dd($Input::all())

Also, could you try with

protected $guarded = ["*"];

and see if the same occurs, maybe something else is going on.

TheFox's avatar

Yes, using Input::all() (or $request->all()). I use the following code:

$fields = $request->all();
unset($fields['_token']);
Post::where('id', $id)->update($fields);

Because when I don't unset _token I got this error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column '_token' in 'field list'
mstnorris's avatar

That's the issue you are having I think, when you modify the Input, as the docs say, you aren't protected.

Have you tried

$input = Input::except('_token');

I would suggest trying the above with the $fillable property. Let me know what you get.

TheFox's avatar

No, that's not the issue. I tried every possibility.

When I use

Post::where('id', $id)->update(Input::all());

I still get

SQLSTATE[42S22]: Column not found: 1054 Unknown column '_token' in 'field list'

because I use

<input type="hidden" name="_token" value="{{ csrf_token() }}">

But what's the difference between

Post::where('id', $id)->update($request->all());

and

$fields = $request->all();
unset($fields['_token']);
Post::where('id', $id)->update($fields);

?

Input::all() === $request->all()

returns true. Because it's the same (type array). And even if you use update(Input::all()) (or update($request->all())) it's type array.

TheFox's avatar

@mstnorris Nope, not working. Even if I use protected $guarded = ["*"]; I can pass all fields to the DB.

mstnorris's avatar

Just to confirm, you definitely tried this?

$input = Input::except(['_token', 'user_id']);

What version of Laravel are you using, I assumed Laravel 5.

Is it up to date? Have you run composer dump-autoload -o ?

mstnorris's avatar

Try

Post::whereId($id)->update($request->except(['_token']));
TheFox's avatar

I tried this:

$input = Input::except('_token');
Post::where('id', $id)->update($input);

No SQL error appeared because the _token field is (like expected) not anymore in the $input array. But I can still pass all fields from a fake post request to the DB.

I run composer dump-autoload -o but everything is working like before.

:vagrant@precise> ./composer.phar show -i | grep laravel
laravel/framework                     v5.0.28 The Laravel Framework.
jekinney's avatar

Generally the rule of thumb is use input::only() not input all. When you explicitly set what data is allowed to be passed insures some level of security. Some eloquent methods do not perform a mass assignment check.

Example is newing up a model, assigning each column and save() will not through an exception.

TheFox's avatar
<?php namespace App;
// app/Post.php
use Illuminate\Database\Eloquent\Model;

class Post extends Model{
    protected $table = 'posts';
    protected $guarded = array('id', 'user_id');
    protected $fillable = array('title', 'content');
    protected $hidden = array();
}

mstnorris's avatar
<?php namespace App;
// app/Post.php
use Illuminate\Database\Eloquent\Model;

class Post extends Model{
    protected $table = 'posts';
    protected $fillable = array('title', 'content'); // removed guarded, I think you only need one or the other
}

then use

Input::only(' ... ');
TheFox's avatar

When you explicitly set what data is allowed to be passed

I thought I would do that anyway by setting $guarded or $fillable?

So I actually don't need to set $guarded or $fillable when I need to use Input::only('...'); anyway. Setting $fillable AND also use the same array in Input::only('...'); is a bit of overhead of code, isn't it? But it works. :)

Can't be there something like protected $htmlPostSave = array('title', 'content'); to avoid the edit of the controller every time I add a new field and to avoid duplicate code (array)?

Thank you for your help.

mstnorris's avatar

You use $fillable or $guarded to protect the model.

When you use Input::only(...) you're sort of doing things twice, but it is good practice.

Please mark your answer if that has sorted it.

TheFox's avatar

Protect the model against what?

It doesn't matter if I use $guarded or $fillable or none of both, I can always set the ID, or User ID in PHP code even if I don't use any HTML:

<?php
$post = Post::find($id);
$post->id = 1;

So what's the protection you talking about? Can you please show me an example code where $guarded or $fillable take effect?

mstnorris's avatar

@TheFox I have set the $guarded property on my User model, and using artisan tinker

~/Code/laravel/myproject $ art tinker
Psy Shell v0.4.4 (PHP 5.6.6 — cli) by Justin Hileman
>>> User::create(['name' => 'test','email' =>'mike@test.com','password' => bcrypt('pass')]);
Illuminate\Database\Eloquent\MassAssignmentException with message 'name'
mstnorris's avatar

There has to be something else going on in your code, as I am protected exactly as expected.

TheFox's avatar

I'm in development mode. I have set APP_ENV=local in .env. Does it matter if I'm in production mode or in local mode?

mstnorris's avatar
  1. Can you post all your code as it stands at the moment
  2. What you expect/want to happen?
  3. What actually happens.
  4. The errors you are getting when you try to execute it.
TheFox's avatar

@mstnorris

  1. https://github.com/TheFox/myblog
  2. I expect an error such as from your artisan tinker output. Because I only set title and content to $fillable in the Post class and I pass all fields to the update function. So actually id or user_id shouldn't be set.
  3. It saves all input fields. You can edit the id or user_id on http://local.dev/myblog/public/post/edit/1 (postedit.twig). Usually I don't have the input fields for id and user_id. I added them now because of debugging. But as I said anybody can add them through for example Chrome Developer Tools and send an invalid post request.
  4. There is no error.
bobbybouwmann's avatar

Looks fine to me... What happens when you have comment out the $fillable array? You should get an error by then!

mstnorris's avatar

@TheFox

  1. I'm checking it over now
  2. Yes I would have thought you'd get that too, however, when you use, Input::all(), it won't produce an error
  3. AFAIK it shouldn't do that
  4. When you use Input::all() it won't produce an error, it will just insert what it can
Next

Please or to participate in this conversation.