Designing Scheduling API0:00
Alright, cool. So, now that we understand the basic process of scheduling various tasks and commands, why don't we, for some bonus credit, take a peek behind the scenes and figure out just how all of this stuff fits together. It's pretty neat, and this is how I like to learn how to use Laravel. So, well, we begin by thinking about the basic API. Imagine that you are building a component like this for yourself. Well, one thing that I always say all over Laracasts is, write it out exactly the way you would want to use it. It's not the craziest thing in the world. So if you want to schedule tasks to be run, well, just open up a new file and write it out exactly the way you would want to interact with it. So maybe we think, well, I'd like to execute any kind of terminal command. So maybe I have
out exactly the way you would want to interact with it. So maybe we think, well, I'd like to execute any kind of terminal command. So maybe I have some kind of exec method, and then I'm often going to be triggering artisan commands, so I could do something like this and call my custom commands, right? But then we think, you know what? I'm always going to be triggering artisan commands. That's a big thing that I'll be doing throughout lots of my projects. So maybe we need some kind of wrapper around that to make it a little simpler. I don't want to have to do this over and over and over. It's not a big deal. It doesn't waste that much time, but it's just that much readable. And from my experiences, that little bit, that little bit makes a huge difference. Because you take that little bit and you multiply it in a thousand different areas of your project,
And from my experiences, that little bit, that little bit makes a huge difference. Because you take that little bit and you multiply it in a thousand different areas of your project, and it turns into a massive difference. So if we were to tweak this, we can still keep this exec method, but tweak it to make the process of calling an artisan command easier. Well, maybe we have schedule command, and then you have something like that. Then we need to specify the frequency, so we could say, well, I'd like to run this monthly, or daily, or maybe daily at a specific time. We could do daily at, like we learned about in the last episode, or you can also just call at if it's a little more readable, like this. All right, cool. So now if we were building this ourselves, well, we've sort of reproduced
Locating Scheduling Source2:01
call at if it's a little more readable, like this. All right, cool. So now if we were building this ourselves, well, we've sort of reproduced that initial research. At this point, we know exactly how we want the API to work in a perfect world. So now we just have to write the source that allows for it. Let's see how Taylor tackled it, and I think he did a really good job. It's only about four files, very readable. He did great work. So we could click through directly to the Schedule class, but I want to take you to the folder manually, so you can see for yourself. We'll go into the vendor directory, into laravel/framework, src, Illuminate, and all of the Scheduling, the Scheduling component, that's located within the Console directory,
We'll go into the vendor directory, into Laravel, Framework, Source, Illuminate, and all of the Scheduling, the Scheduling component, that's located within the console directory, which makes sense. And now we can see, yep, we have a Scheduling directory, and we also have our SchedulingServiceProvider, where we register a single command. We'll come back to that in a minute. What I'd like to tackle right now, though, is that Schedule class that we use within the Kernel. Just to make sure we're all on the same page, when I say scheduleCommand, what does that look like? All right, let's see what we got here. Well, we already talked about this. We have a general exec method, where you execute any kind of terminal command, or command line operation.
Schedule Events Array3:11
All right, let's see what we got here. Well, we already talked about this. We have a general exec method, where you execute any kind of terminal command, or command line operation. Now when we take a look at that, well, it looks like we're just updating an array. Add a new item to an events array on the object, and we will pass in the command to a different event class. Okay. So that means when we call schedule command, well, command, that method, is just a little wrapper around this. And all we're doing is saying php artisan, and then the name of the artisan command that you pass through. That's what I'm talking about. Just little tweaks. This didn't even have to be there. You always could have called this. But we make a little tweak to make it that much more easy for us to use. That makes all the difference.
How schedule:run Works3:56
this. But we make a little tweak to make it that much more easy for us to use. That makes all the difference. All right, so now we know when we call schedule command, and we can do that as many times as we want, well, all we end up doing is updating an events array on this schedule object. Cool. So we'll take a look at the event object in a second, but let's go over to the service provider. Remember, this is where we bootstrap everything. And if I look around, well, this can be confusing to me. We register our artisan command, but what about the process of just binding things into the IOC container? And here's what I mean by that. If we look at our commands and see what exactly happens when we call it every single minute, well, we come down. It's pretty basic. When it fires, well, we fetch all events that are
our commands and see what exactly happens when we call it every single minute, well, we come down. It's pretty basic. When it fires, well, we fetch all events that are currently due, events that are ready to be fired. Think of it that way. And then we say for each one of them, we run the event. Okay, well, how do we figure out which events are due? We'll switch through to the Schedule class again. And now we can see that events array that we update. Remember, you could call that multiple times. So there could be two schedule commands that you run. We're going to filter through those, and we just want to break that down to a smaller array of only the ones that are due. So this makes sense, right? Filter over the events array and just return to me the ones where each event is due. Now, if we want to very quickly figure out if an event is due, well, you can
sense, right? Filter over the events array and just return to me the ones where each event is due. Now, if we want to very quickly figure out if an event is due, well, you can see that mostly Laravel defers to a third-party package. So you can see we have a quick little check to see if it runs in maintenance mode. We then see if the expression passes. And notice that we're deferring to a third-party package. And that one is responsible for figuring out is this particular task due to be run. And notice that we pass in the expression. That's literally going to be something that Laravel constructs. It'll be one of these things. So that's what we pass to the third-party package. And we ask it, given that cron job, what is that due to be run? And we pass in the current timestamp. That's how we figure it out. Next, we do a little filtering, and then we make sure that this is a command that
what is that due to be run? And we pass in the current timestamp. That's how we figure it out. Next, we do a little filtering, and then we make sure that this is a command that should run in the current environment. So should this be run in production? And that's something you can configure when you schedule these tasks. Okay. So that makes basic sense. But here's what would confuse me. Well, if you're anything like me, this is what would confuse you. We have a Schedule object, right? And when we call it, we build up this events array. Okay. Makes perfect sense. But then we have a schedule->run command, and that accepts an instance of Schedule. But how do we know that's the same object as the one that you're building up within the Kernel class? Do you see what I mean there? You build it up here on that object or that instance. But now in schedule->run, we're requesting a different
Kernel Binds Schedule6:51
building up within the Kernel class? Do you see what I mean there? You build it up here on that object or that instance. But now in schedule run, we're requesting a different instance. We're requesting a new instance of schedule. And then we're filtering over that events array within that schedule object, which would be empty, right? It's a new object. So how exactly does this work? And further, if you're anything like me, well, you would look for that answer within the ScheduleServiceProvider, but we're not really seeing anything, right? Here's the key. We're going to go back up to our ConsoleKernel. And if we scroll up, you'll see that we extend, well, this class right here. So let's see what that's doing. All right. Now if we scroll down, we have our typical bootstrappers. We haven't covered that yet, but we'll talk about how all of that works in a future video. But now I want you to see
All right. Now if we scroll down, we have our typical bootstrappers. We haven't covered that yet, but we'll talk about how all of that works in a future video. But now I want you to see right here. Okay. When the app has booted, we want to define the console schedule. And when we call that method, okay, now this is starting to make sense. We want to replace any instance of this Schedule class or this key that represents that with a new Schedule object that we define right here. And then notice that we call a schedule method, which means if we come back, this method is fired as soon as the app is booted. Okay. So now that sort of makes sense, right? We're saying that instance of that class is equal to new Schedule. So that means within our schedule run commands, when we request this, we're getting that new Schedule object that will be populated with all of the events. Okay. I hope that makes sense. I wanted
Event Cron Expressions8:28
means within our schedule run commands, when we request this, we're getting that new schedule object that will be populated with all of the events. Okay. I hope that makes sense. I wanted to go over that because it confused me as well. So we fetch our doEvents, and for each one, we run the event. Now, before we figure out what's involved with running an event, well, why don't we go back to Kernel and we figure out, well, what happens when we create that new event? Well, we have a command. We know that defers to the exec method. That appends to an events array where we fetch the do ones later. And then we say new Event, and we pass through, and don't forget, that would be something like laracasts/sample in our example. Okay. Let's look at the Event class. And let's see. Well, it looks like we define a base expression here. It probably does some search and replacing later to build up the
in our example. Okay. Let's look at the Event class. And let's see. Well, it looks like we define a base expression here. It probably does some search and replacing later to build up the correct cron job. So for example, if you want Mondays or Tuesdays, then we update one of these. Okay. And that will be our expression that we pass to the third party package later. And now we have some additional properties here. The default output. But I bet we could change the output. And if we check out that sendOutput method, we'll take a look. You pass the location, we update the output property, and you're done. Okay. Cool. Now, if we go back to the constructor, there's the command that we're passing through. So we know that works. And next, we know that we're calling any of those various methods like monthly, or daily, or at, or Mondays and Tuesdays. Why don't we just look at Mondays? When we call that, well, it defers to a days method.
we're calling any of those various methods like monthly, or daily, or at, or Mondays and Tuesdays. Why don't we just look at Mondays? When we call that, well, it defers to a days method. And if we take a look at that, well, remember, this would be something like one. And then we splice into position five. That would refer to, well, basically, when you have *, *, *, *, *, *. All Taylor is doing to make this work is saying, well, take the day number here, and we're going to splice that into the fifth position. And that will represent our cron expression. And if we take a look at that, if you're curious, yeah, we just explode up the expression into an array. And then we fetch the, well, not the fifth position, but the fifth key within that array of *s. And then update it to the days. And then we call the cron method, and we pass in our expression. *, *, *, one, *, *, et cetera. So we can see that
within that array of stars. And then update it to the days. And then we call the cron method, and we pass in our expression. Star, star, star, one, star, star, et cetera. So we can see that there's a master cron method. And again, this just updates that expression property. That's all it does. So what about if we call monthly? Okay, well, once again, we just call that cron method, we update the expression property, and make it equal to this. So we can see this is nothing more than a wrapper around that process of defining your cron jobs. But can we agree that calling a monthly method is so much easier than having to remember exactly what that means as soon as you get to work and your mind's not even working just yet? Okay, so now I feel like this is starting to make a little more sense for us, right? When we execute a command, we update an events array. And when we instantiate a new event, and eventually call one of the methods like monthly,
make a little more sense for us, right? When we execute a command, we update an events array. And when we instantiate a new event, and eventually call one of the methods like monthly, well, we update an expression property, and that's all we do there. So now, when we actually trigger the schedule:run command every single minute, we fetch the ones that are due. We're going to go through this again. For each event that has been defined, just return to me a subset of the ones where each event is currently due. And when we take a look at that, well, we call expressionPasses. And once again, we reference that third party package that figures out if it's due. And really, it's just kind of, it's not even overly pretty to look at. It just does a lot of checks to see, is this what we want, splice it, all of that crap. We don't really need to look at that. But the important thing is, we pass through the cron job expression that we built up with our
Executing Scheduled Commands12:18
checks to see, is this what we want, splice it, all of that crap. We don't really need to look at that. But the important thing is, we pass through the cron job expression that we built up with our little API. So now, the only remaining thing I think we need to figure out is how we actually run the job. Let's go back to our commands. We fetch the due events. And for each one, we run the event. And we pass through the application, the current application instance. Okay, well, let's reference the event object again. And to the run method. Now, we check to see if we have any after callbacks registered, like do this after. Don't worry about that. But we're going to run in background. And if we take a look at that, well, we're just going to reference some basic php functions for working with the command line. We begin by saying, change the current working directory to Laravel's base path. And that's how you're able to do php artisan
some basic php functions for working with the command line. We begin by saying, change the current working directory to Laravel's base path. And that's how you're able to do php artisan example. And you don't have to reference the full path to where artisan is located. We don't have to do that, because the current working directory is our project directory when we run this. Next, we build up the command, and we execute it. That's a basic php function that you can use anywhere to execute a terminal command. When we build up the command, well, we have a check here. This is another part of the API. If you want to make sure that nothing overlaps when you trigger these, for example, if you have a command scheduled to be run, but you have a previous command or operation that's still running, well, there would be overlapping, right? If that's okay, then you can specify it. And if it's not, you could say
but you have a previous command or operation that's still running, well, there would be overlapping, right? If that's okay, then you can specify it. And if it's not, you could say otherwise. And we just build up our command here that we eventually process. And notice we take the command that you had provided, and then this is what we talked about before, the same thing. We send it to the output, which will be just null, discard it, and take the error, and send that to standard output as well. Finally, if we have a $user set, well, we want to run that as sudo. Otherwise, we just return the command that we built up. So really, not the hardest thing in the world, right? If we switch back to run, well, don't forget, we run it in the background, we built up the command, just sort of some concatenation, really, at that point, and then you pass it to php's exec function, which triggers the commands,
we built up the command, just sort of some concatenation, really, at that point, and then you pass it to php's exec function, which triggers the commands, but with the benefit of having this beautiful API that makes it as simple as possible.
