#2 [Illuminate2] Model Guards Attributes on Create
In my first article a few weeks ago, I covered the benefits of a Model-centric approach to Laravel. If you haven’t already, check out that article here.
This week is the second in a long line of tutorials for the Model-centric components I’ve built over the past several months. If you read my first piece, you’ll know that all of my components were originally developed as the result of mish-mash experimentation and hacking something together until it did what I wanted. As I publish this tutorial series, I’ll also be cleaning things up by re-developing each of the components into my new package, helium/illuminate2
.
Guards Attributes On Create
One short and convenient modification to the built-in Eloquent Logic that I’ve developed is the ability to conditionally apply $fillable
and $guarded
based on whether the model instance is being created or updated. In my experience, it's a fairly common requirement that certain model attributes be fillable when the instance is created, but guarded from changes forever afterwards. For example, consider a class to represent Comments on a Post. The Comment object has a user_id
and a post_id
which must be set when the object is created, but should never be modified thereafter.
Installation
To get started, install the package into your Laravel project:
composer require helium/illuminate2
Please note that this package is only compatible with Laravel 8.14 and later, so you should make sure your project is up-to-date with the most recent release.
Fillable On Create
Include the GuardsAttributesOnCreate
trait on your Model.
use Helium\Illuminate2\Database\Eloquent\Concerns\GuardsAttributesOnCreate;
use Illuminate\Database\Eloquent\Model;class Comment extends Model
{
use GuardsAttributesOnCreate;
}
Great! Now all that’s left is to declare your fillable
and fillableOnCreate
properties.
protected $fillableOnCreate = [
'user_id',
'post_id'
];protected $fillable = [
'comment'
];
In the given example, the $fillableOnCreate
property declares a list of attributes which are only mass-assignable if the object is being created (i.e. not updated). And, as always, the $fillable
property declares a list of attributes which are always mass-assignable.
In Practice
Consider how this affects the usage of your model class in practice.
$comment = Comment::create([
'user_id' => 1,
'post_id' => 1,
'comment' => 'Hello, World!'
]);$comment->update([
'comment' => 'Hello, Laravel!'
]);
In the given example, you are able to mass-assign the user_id
and post_id
while creating the Comment
instance, but once they're set, any attempt to mass-assign those attributes on a call to update
will result in the changes being ignored.
Guarded On Create
Although I can imagine the use case for this is much less likely, for the sake of completeness, I have included the option to set a list of attributes which are only guarded on create
. Perhaps you have a Post
model which must be created as a draft before it can be published. In this case, you may wish to guard the published_at
attribute on create, but unguard it for updates. As you might expect, you can set the guardedOnCreate
property to define properties which are only guarded from mass-assignment on create, while the guarded
property continues to guard specified attributes from mass-assignment at all times.
use Helium\Illuminate2\Database\Eloquent\Concerns\GuardsAttributesOnCreate;
use Illuminate\Database\Eloquent\Model;class Post extends Model
{
use GuardsAttributesOnCreate; protected $guardedOnCreate = [
'published_at'
];
}
Customization
As with all of the Model-centric components I’ve developed, you can customize the fillableOnCreate
and guardedOnCreate
attributes by overriding the requisite getter functions. This would be required if, for example, you wanted to conditionally add attributes to fillableOnCreate
or guardedOnCreate
based on some external state, such as circumstances defined by different Controller endpoints.
Please note that the $creationState
property shown here is not part of the GuardsAttributesOnCreate
trait. Instead, it is intended to demonstrate one the many ways in which you could customize the logic of these getter functions to meet your circumstantial needs.
class Comment extends Model
{
use GuardsAttributesOnCreate; public static $creationState = 'parent'; public function getFillableOnCreate()
{
if (static::creationState == 'child') {
return ['parent_id'];
}
} public function getGuardedOnCreate()
{
if (static::creationState == 'parent') {
return ['parent_id'];
}
}
}class CommentController extends Controller
{
public function create(Request $request)
{
return Comment::create($request->all());
} public function reply(Request $request)
{
Comment::$creationState = 'child';
return Comment::create($request->all());
}
}
A Word On Philosophy
Model-centric logic isn’t for everybody, and for your own purposes, you may find it more effective to simply use the mergeFillable
and mergeGuarded
functions provided by Eloquent out of the box. However, for most straightforward use cases, this approach has worked quite well for me and my team, as the logic of determining mass-assignable attributes almost never changes circumstantially (except between create
and update
, which is the entire purpose of this tutorial). This tutorial is intended for developers who can benefit from this alternate approach based on their own philosophy and style, and if this approach isn't for you, I hope this at least sparks a lively and respectful conversation in the comments!
To understand my particular approach on using Laravel, I encourage you to read my opinion piece The Case For Model-Centric Laravel.
Conclusion
Again, this is the second in a series of tutorials covering the various Model-centric components I’ve developed over the past several months. Check back in in two weeks to see the next tutorial on generating Model primary keys.