5 Tips and Tricks for Laravel: Models
Oliver Sarfas • April 2, 2019
programming laravelLove it or hate it, Laravel's Model layer is one of it's biggest positives and reasons to use it. It comes shipped with the Eloquent ORM which is an Active Record implementation.
A lot of the functionality that it comes with is very easy to understand, and clearly documented, so I'm not going to bore you with the basics Model::all()
, and all that stuff. Here's a few pieces that I've stumbled across, or am really enjoying using lately! I hope they can help you in your project too.
Model::firstOrCreate()
For argument's sake, we have a set list of State
for instance. So we loop through our 4 states, and have them all created. Now, what if they already exist? We don't want my data to be duplicated.
Instead of calling; foreach($states as $state) { State::create(['name'=$state]); }
we call State::firstOrCreate
. This ensures that we do not create the same state twice. Perfect for setting up our database. It's also very helpful for adding tags to blogs, as user's may want to create a tag on-the-fly as it were.
For completion, here's a code snippet showing what I mean
$model->touch()
If you use the Laravel Scout package, you'll often find that your search indexes will fall slightly out of line over time. There's many reasons for this, but primarily they'll be Model Relationships not cascading up or down, or someone manually fiddling the database - naughty but come on, we've all done it!
->touch()
makes this a lot easier to control. I have a User
, who has been soft-deleted. I want their account to be reinstated. Normally, you'd do this via your Web GUI, or through Tinker; however sometimes, you set the deleted_at
field to NULL instead, it's far easier!
This will not trigger an update on the model, so your search index is not updated. To remedy this, you can run $user->touch()
in Tinker. This will force an update()
on the model, and thus update the index for the given user.
It is worth looking into the protected $touches
array on any relationships as well, so that when you add a new Contact to your User, the User model is updated in the search as well.
with
and appends
Our Models have relationships. I've yet to find a single code base that doesn't have them, it's part of software. Sometimes, we'll want to see the results of these relationships. Using our previous example, our User has Contacts, many of them in fact.
I might want to display all the User->Contacts->name(s) on a webpage. Now, I could just do this in a loop, however I then come into N+1 query issues. Where I load the User, and then load each Contact one by one. Not very clean.
In this instance, I'd want to use the with
function. See the below;
Appends is a similar concept, except it runs on attributes, instead of relationships.
Often we will want to know if a User is an admin, and do not want to run a if($user->admin === true)
check, so we just want a property on the model called $user->is_admin
. For this we'd do;
Note: Avoid accessing relationships in your custom attributes, as you can add hidden N+1 issues within them, and they're a nightmare to troubleshoot
Events, ::boot()
and Observers
Often you will want automated activity to happen based on something that happens with your Model. There are many ways of achieving this, firstly, you can trigger an Event.
On your model, you can declare the dispatchesEvents
property, which lists what Events are triggered, when a ModelEvent happens. For example, you might want to trigger an event whenever a new user has been created. From this event, you can subscribe Listeners and have processes begin
If you don't like the $dispatchesEvents
method, you can also register your events using the boot()
method in your model. The code would look like this;
A further method to do this, is to extract the logic entirely out of the Model, and use the Observer pattern. These are a little more fiddly, but offer a more "correct" approach when considering the Single Responsibility Principle.
To start, you generate an Observer, you do this via Tinker, with the command, php artisan make:observer UserObserver --model=User
. This will create a PHP class called UserObserver in your app\Observers
directory. You need to register this Observer with the model
Equally you can register it in the boot
method of one of your ServiceProviders with the following;
The Observer class will come preloaded with lots of functions that will be triggered when their respective Model event is triggered. This isolates the functionality away from the Model, and will keep your code cleaner in the long run.
getRouteKeyName()
This one often goes forgotten, or is used so much, you don't even realise you're doing it. getRouteKeyName
defines the Route Model Binding column for the model. In English, this means that instead of you doing /users/{user}
where {user} is the id
, you could use the email
if you like - just be sure to use a unique field!
For the example above, you'd define the User model as follows;
Now whenever I use the Route Model Binding with a User class, I use the email
as the identifier, and not the id. Great for not exposing your ids in the URL, which is often considered a security concern in today's market.