Playing
01. The defer() function

Transcript

00:00
The new defer function allows us to take slightly longer running processes and push them behind the scenes to a background PHP process. What we're going to do in this episode is take a look at how this works. Then we're going to dive behind the scenes to see what is actually happening.
00:16
We're then going to look at a practical example of where you might use this. And then we're going to look at when you probably shouldn't use this and an alternative. Okay, so let's get started. We're going to go ahead and invoke the defer function here.
00:30
And you can see that this gives us a callback. Let's go ahead and just do something in here like log out to our Laravel log file. And let's just write logged in here. Now, if we go over to the browser, give that a refresh.
00:44
Sure enough, that closure would have been run. If we go ahead and open up our log, you can see, sure enough, we get that logged. So what is the point of this? What's actually happening here is as long as this request was successful,
00:59
so for example, if it had a 200 or a redirect status code, this defer function would have been pushed to the background. Now, at the moment, it's pretty difficult to demonstrate this because logging something doesn't take that long.
01:12
Let's just simulate a slightly longer request and see what happens here. So I'm going to go ahead and sleep here for five seconds. Let's make sure that the Laravel log file is completely clear. And we're going to go back over to the browser and give that a refresh.
01:25
And you can see that was instant. So this didn't sleep. If we head over to the Laravel log file, you can see at the moment it's empty. But after five seconds behind the scenes, once that delay has kicked in,
01:36
we will see this logged out. So basically, anything that is going to take a little while within any of your requests can be pushed to a background process. So this isn't being queued.
01:48
It's not being pushed to a queue. It's just basically going behind the scenes inside of a PHP process. So anytime you want the UI to feel a bit more snappy and you have a task that would potentially slow that down,
02:00
you can just stick this in a defer function. Now, this can be used multiple times. And let's just dive into how this works behind the scenes. So if we come over to the defer function, the definition for this,
02:11
sure enough, we've got our callback here. We also have a name here that we can give this. And we have this always flag as well. Now, we'll talk about always in just a second
02:19
after we've dived into how this works. But let's go ahead and see what this returns. So it returns the defer function from the support here. So this is basically going to be exactly the same thing.
02:30
OK, so let's open this up. If the callback is null, we go ahead and just return this deferred callback collection, which we'll talk about in a second.
02:38
Otherwise, what's happening here is we're creating out a new deferred callback. And this is being pushed to this deferred callback collection, which we're returning here if we don't have a callback.
02:48
Now, we're not going to worry too much about this callback here just for now, not being there. Let's open up this deferred callback collection and see what it has. So this is just a really basic class that implements array access.
02:59
So basically, it can hold a bunch of items. It's kind of like a Laravel collection. We can grab the first one. We have this invoke, which we'll take a look at in a second.
03:08
Invoke when, which we'll take a look in a second. Forget. All of the stuff here, which manages basically just invoking any of these callbacks, which
03:16
have been pushed to this class. And we have things like offset exists, offset get, which is part of the SPL interface that's been implemented here. OK, so when does this happen?
03:26
When is the deferred callback collection accessed? Well, that's after the request was successful. So think of it this way. When we go ahead and refresh the page, we have a 200 successful status code here.
03:39
Of course, nothing has errored. What this is going to do is use a piece of middleware to start to invoke these callbacks in background processes once the request was successful.
03:50
This middleware can be found under invoked deferred callback. Either way, let's go ahead and take a look at what happens. So we have a handle method, which just does the standard thing. It doesn't do anything in here.
03:59
Just returns the next request. In here, we have this terminate method. So what this will do is this will run, or this will always run. But we're going to grab the collection, which we've just looked at,
04:13
and use that invoke when method that we saw within that collection. This is just a conditional invoke of all of the callbacks, which have been stored up in the current request. That's why we can use this defer helper multiple times.
04:25
So this will only invoke these callbacks that we've pushed to defer when the status code is less than 400. So if it's a 200 status of OK, or if it's a 302 redirect or a 301 redirect, this will be run, unless we provide always as that flag.
04:44
So let's go ahead and look at the invoke when and see what's actually happening here. So as you would expect, we're iterating through all the callbacks in this collection.
04:52
We are going ahead and using rescue to run the callbacks. So rescue is a method that will silently fail this if it doesn't work for any reason, which is really important. So basically, just run this.
05:05
So all this is doing now is running this in the background after the request has been successful in a separate PHP process. We're not going to dive too much into the background of that, because it can get pretty complex.
05:16
But really, that's all you need to know, that it runs in the background. Now, the downside to that is you don't want to do anything that runs like you would normally put into a job. We don't want to push something like a video encoding job inside of defer.
05:30
We're going to take a look at an example of how we can get around that with an inline dispatch of a job a little bit later. But that's pretty much how that works. Now, let's take a look at this name and always.
05:40
So what we could do is, even if we get a unsuccessful response here, so for example, if we return a response here, let's say it's a 403 forbidden. So let's just put null as the body and a 403.
05:52
Let's open up our Laravel log, get rid of these two. I'm going to get rid of the sleep just so we can demonstrate this. But because this request is not going to be successful, we hop over to the browser and give this a refresh.
06:03
We get a 403. What's going to happen here is that will have not been logged because of that condition under invoke when. Now, if we go ahead and change this up so we can set a name,
06:13
I'm just going to set that to null. And we set true as always run, regardless of the status code. And we give that a refresh. You'll notice that that does get logged.
06:23
So you have control over if this is logged, even if you get an error, or even if this is run, if you get an error. But by default, this will not be run unless we have a successful request. Now we know how this works.
06:34
Let's take a look at a practical example of this. And this is actually something that I personally use defer for. So what we're going to do is we're going to log the last activity of the user. So whenever they make a request to any page on our site,
06:48
and we're going to log that in the database. Now, this is, of course, something that is going to create a query every single time the user lands on another page. So it's perfect for deferring to the background
06:59
just to save an extra query on each request. Let's go ahead and build this whole thing out now. And then you can use this if you want to. OK, so the first thing that we're going to do
07:08
is go ahead and just make a user in the database. So let's go ahead and use Tinker here. And we'll go ahead and use the user factory. Create a user out.
07:18
And we'll use that to authenticate that user as an example. OK, so now that we've done that, let's go ahead and add a column to the users table. So let's make out a migration.
07:27
And let's add in the last activity at to users tables. This is just going to be a timestamp that logs when the user last hit the page. So add last activity at to users table.
07:38
And of course, if we come down to the up method here, let's go ahead and just add in a really simple timestamp. So let's say timestamp. And we'll say last activity at.
07:51
And let's go ahead and just add this to the user model. We'd probably add this as a fillable and also a date. So let's add last activity at. I'm not going to bother adding it to the dates of this
08:02
just to save some time. OK, so now what I would do is go ahead and create out some middleware. So let's go ahead and make out some middleware.
08:10
And we are going to call this TouchedUserLastActivity. So this will just run for every single request and just update that timestamp for a user as long as they're signed in.
08:20
So let's open up the TouchedUserLastActivity middleware. And let's go down here to the handle method. And as long as the user is signed in,
08:29
we can go ahead and do this. So let's say if request user, and why don't we just assign that at the same time. We're going to go ahead and just update the user.
08:40
And we're going to set last activity at to the current date and time. So let's go ahead and register this over in Bootstrap and app. And we'll come over to our middleware section.
08:53
Let's say middleware. And let's just say we just wanted to do this for web. We could go ahead and append this on. Let's pull this down.
09:01
And we're going to append on TouchedUserLastActivity. And there we go. So that's going to run for every request. Now, I've just installed a plain Laravel app.
09:10
So I'm just going to go ahead and fake this user. So let's say auth login using ID 2, which should be the user ID in the database here. And it is.
09:18
And now when we hit this page, we should get this logged out. So let's get rid of this defer function in here just for now. And let's respond with a 200. OK, last of all, let's just run our migrations here.
09:30
And if we head over to the browser and give that a refresh, we should have that last activity logged just in here. Now, I don't really need to do this
09:37
to show you that a query is being run. But let's go ahead and require in Laravel debug bar just so we can see what we've got here. And to use this, we're going to need to return a view.
09:47
So let's go ahead and run php artisan make view. And we'll just call this home. And let's swap this out to return a plain home view. OK, so if I give this a refresh under queries,
09:59
sure enough, we have an update query in here. Let's go ahead and defer this to the background inside of a PHP process. So to do this, it's really up to you.
10:08
You could do this all directly in the middleware. So you could go over to the touch user last activity middleware. And you could use the defer helper directly in here
10:17
if you wanted to. Or you could switch out and add it as a method over on the user model. It's entirely up to you.
10:24
So let's go ahead and do this now. And then we'll just swap this around. So let's go ahead and use the user within this closure. That's going to go ahead and run that.
10:31
Just for clarity, let's go ahead and just open this up a little bit more, just so we can see what the value is. So it's 832. Let's go back over and give this a refresh.
10:41
We only get two queries. But hopping back over to the database, sure enough, this has now been updated. So we've saved a query.
10:48
Sure enough, it's not going to make things much faster. But it saves a query for each request. And for something like this, a really simple insertation into the database is the perfect thing
10:58
to push to a background process, rather than have to set up queues to go ahead and push this entire thing in. OK, let's just tweak this around the way
11:07
that I would probably do this. So I would probably, if I was building this out, I would take this entire thing. And I would just add this to a method on the user.
11:17
So I would just say something like touchLastActivity. And let's hop over to the user model and just put this somewhere down here. So let's create out a method called
11:26
touchUser or touchLastActivity. And we're just going to paste that entire thing in here. Of course, we don't need to bring the user into scope. We can just reference this to update this user.
11:37
So that's going to work in exactly the same way. Let's go back over to the browser, give it a refresh. Sure enough, we still get two queries. And that value has been updated.
11:45
So for small things like this, it's really, really useful to push very small things to the background. If you're processing, if you have a class that processes
11:54
a bunch of data in the background, you could just very easily defer this now. So now that we've looked at this, you should have a pretty good idea of the kind of things
12:03
that you could use this for. But if you have any longer running things that you want to push and you don't think defer is suitable, you can actually inline dispatching stuff into your queue.
12:17
So let's take a look at that now if you're not aware. If you're already doing this, feel free to end the video. So we already have queues set up in a fresh Laravel application.
12:27
If we just look at the queue connection by default, and we have our jobs table in the database as well, let's go ahead and push something using a inline dispatch.
12:36
So we have a dispatch helper. So it works very similarly to defer. So we can just dispatch something to our queue and do something in here.
12:45
So for example, let's go ahead and log out logged from queue inside of here. OK, let's go over to our Laravel log, get rid of everything in there, and let's go ahead and run this.
12:57
So by running that, that's going to push that to the background. Of course, we get an extra query because we're running database queues.
13:03
But if you were using Redis for this, obviously, it would be a lot faster and you wouldn't have a database query. But that's been pushed to the database now.
13:11
And that will now be picked up by your queues when you run them. And it's perfect for any longer running things that don't feel right with defer.
13:20
So let's finally just go ahead and run our queues using phpArtisan queue work. And we will see that run. That closure has been run, pushed to the queue
13:29
as a closure. And if we open up our Laravel log, sure enough, we've logged that from queue. So if you do need to do something in line,
13:36
you don't want to create a job for it, or you don't need to create a job for it, but it doesn't quite feel right for defer, you can just use dispatch inline.
13:43
Otherwise, as we've already seen, defer is super helpful for pushing things like this to a background PHP process without the hassle of queues.
26 episodes2 hrs 34 mins

Overview

Need to know what’s new in Laravel as it happens? Every episode of this course is dedicated to covering the most interesting and useful Laravel additions in detail, so you’re ready to start using them in your applications.

Check back often, and stay completely up-to-date with Laravel.

Alex Garrett-Smith
Alex Garrett-Smith
Hey, I'm the founder of Codecourse!

Episode discussion

No comments, yet. Be the first!