I've been using Laravel (5.4) and PHP both for the very first time in a legacy app I've been tasked to maintain, and over the past couple of months I have noticed a pain point, which is mass assignment.
I believe I have a well-enough grasp of what is is, but I am not sure if I am really utilizing it correctly. The official documentation illustrates the problem using a (good, but extreme) example of a user's role. I understand the significance of this and why I should guard such variables. However, I don't think I really have a solid understanding of determining what fields to guard or not (if at all), in general.
It seems that a lot of the nice features of Laravel don't seem to have mass-assignment in mind, which is the main source of my frustration. For example, if I had an endpoint that had some optional fields (i.e. you could just not specify them in the request), then doing a mymodel->update(request->all())
would only update the fields that you provided. If many of the fields were guarded, I would have to have many repeated isset() checks if I wanted to achieve the same behaviour, which seems unnecessary. I also know that request->all()
shouldn't really be used like this, but I am just trying to illustrate a point.
It becomes more cumbersome when you consider that you're probably going to use robust validation alongside something like request->only([...])
to make sure your data is correct and filtered to only what you expect.
So ultimately what I am asking is:
In the presence of robust validation and input-shaping methods, is mass-assigning nearly anything still worth doing? In general it seems that I am jumping through so many more hoops for no reason, when it seems that my validation steps already took care of the problem. If it is still worth doing, what am I missing? Should mass-assignment protection only be delegated to super-important fields, like a user's role, and nothing else?
CodePudding user response:
Actually i didnt understand your mean by this sentence:
If many of the fields were guarded, I would have to have many repeated isset() checks if I wanted to achieve the same behavior
Not Mass-Assignable fields, just get discarded when presented in the request
But in the scenario when user pass a parameter like forbidden_param
from the request and forbidden_param
is not mass assignable ( either is not in fillables or it is in the guarded) there would be no error thrown, and the eloquent is going to discard the forbidden_param
.
if you had this in your code:
if(isset($request->forbidden_param))
you can get relieve from them, when using mass assignable.
Laravel Lets you to do a job any how you want
Laravel just doesn't force you to use helpers or Facade or DI , ... to use a service.
for protecting the Models fields you can come with different solutions to:
1.Eloquent protection (Mass Assignable)
you can pass the parameters from request, then protect them from the eloquent.
//in controller
MyModel::update($request->input())
// in model
$fillable = [ ... ]
//or
$guarded = [ ... ]
it is a common pattern & I have seen lot of codes using this scenario.
The benefits are:
- leaning the controller
- not worry about fallible fieleds, if you just forgot to control input in other places (controller / validation)
2.Controller Protection
another pattern which I have seen is placing the exact parameters in the update
method, in the controller.
Model::update([
'permitted_param1' => $request->permitted_param
'permitted_param2' => $request->permitted_param2
]);
in this case one would relax the mass assignable in the model with:
$guarded = [];
or can benefit it, in case of if another teammate on another controller may would forgotten to control the inputs & use $request->input()
.
this would be optional though.
benefits of doing this:
- Ability to have different input names and database field names
- Ability to manipulate an input before put into model (which can be achieved using mutators or repository ). but in case of few fields to change it is simpler to implement.
in example below, controller use a mixed approach:
User::create($request->except('password') [ 'secret' => bcrypt($request->password)] );
3.Protecting in Validator
One of the most promising methods, I have seen for this, is to use only validated data, like below: for Laravel 8 :
MyModel::update($request->safe()->all());
and for Laravel before 8:
MyModel::update($request->validated())
and you can either relax the mass assignable
Benefits:
- you have combined the validating and sensitizing data in one place
- leaner controller
- you are sure that you have validated all your data
Note: in case of optional data you can just not to put the
required
rule
4.Repository Pattern
another approach for this, is to use the Repository Pattern which can introduce complexity for small projects.
but results in :
- leaner controllers & models
- move query logic in repository
- make queries more reusable and decoupled
for projects using this design pattern, this control can be move to repository.