مرور آزاد کردن منطق از کنترلر0:00
Alright, so have a look at our InvoiceDownloadsController. This looks pretty good. And you can imagine somewhere in your settings page for invoices, you could create a hyperlink that says Download. When the user clicks on it, it will hit the corresponding route, it will trigger this action, it will take a snapshot of their invoice, and deliver it to them. And then of course, instead of returning done, you might return a redirect that displays a flash message, hey, go check your email, you should have your invoice shortly. Okay, so again, this works great. But sometimes you need to consider alternative entry points into this logic, that right now,
Need alternative entrypoints1:37
So I could do that. But there's a number of steps, and it's a very common request. So in these cases, I would always prefer to have a php artisan command that will take care of the entire process in one second, instead of a few minutes, maybe. Maybe something like php artisan lc:sendLatestInvoice. And then maybe we provide the account email or ID or something like that. And then that command would need to defer to this logic. So think about it.
And then that command would need to defer to this logic. So think about it. If we want to allow for different or alternative entry points, another one as an example might be you want to trigger this logic as a result of an incoming webhook. Maybe when your billing provider pings a URL to let you know the invoice has been paid, in response, you want to trigger this logic and deliver an invoice to the user. That's another entry point. But again, the problem right now is we've locked all of this code behind a controller. Now what I often see newcomers do is they will ask questions on forums that amount to something like, how do I trigger a controller action from somewhere else in my codebase?
Extract controller logic2:41
Now what I often see newcomers do is they will ask questions on forums that amount to something like, how do I trigger a controller action from somewhere else in my codebase? For example, an artisan command. Hey, I have this artisan command. How do I trigger a method on my controller? And of course, a controller is just a class like anything else. There's nothing keeping you from triggering an action. But also the arrows are sort of pointed in the wrong direction. When you feel like you want to trigger a controller action's logic, instead, extract that logic into a different class, and then have your controller and your artisan command defer.
Create artisan command3:09
When you feel like you want to trigger a controller action's logic, instead, extract that logic into a different class, and then have your controller and your php artisan command defer to that class as a dependency. That's generally the way to deal with it. All right, so too much talking out of me. Let's give it a shot. Let's create a new artisan command called sendLatestInvoice. All right. So the signature would be sendLatestInvoice. And again, the description, send a user their most recently generated and paid invoice.
So the signature would be sendLatestInvoice. And again, the description, send a user their most recently generated and paid invoice. Okay. So right down where we handle it, yeah, why don't we ask the user something like, what is the ID of the account? Or what is the email address for the account? And then that will give us an email. And then I could say user firstWhere, but I do want to fail. So why don't we say User where the email address matches up here, and give me the first one or fail.
So why don't we say User where the email address matches up here, and give me the first one or fail. All right, and let's dd the user's username. And we'll give this a quick try. So php artisan lc sendLatestInvoice. The email I think is hauser@example.com. And the username is Douglas Quade. All right, so we did find that account. So now we have a couple choices, right? And this is the fun part.
So now we have a couple choices, right? And this is the fun part. It's where we get to think about how we want to interact with our code base. For example, it might be nice to offer something like sendLatestInvoice. Keep it really simple. Some people may not like this. They don't like the idea of email delivery being part of their domain or their model. You know what? I'm not a stickler. I'll come back to this a year from now.
I'm not a stickler. I'll come back to this a year from now. I know what that does. You know, it's not confusing what this line of code does. Another option might be to lock that behind, for example, the User's subscription. So maybe you have a method called subscription that returns a Subscription class or object, and then you call sendLatestInvoice on that. That might be an option as well. Or alternatively, if you don't want it behind your User object, maybe you just dispatch a job.
Move logic into job5:27
Or alternatively, if you don't want it behind your User object, maybe you just dispatch a job. And in fact, even if you did add a method to your User class, you'd probably still dispatch a job. So with that in mind, dispatch job, let's extract the logic that we see here into a job. All right. php artisan make:job sendInvoice. Again, I'd probably tweak some of these things when I had more time to sit and think when
And maybe I'll call it sendInvoice. Again, I'd probably tweak some of these things when I had more time to sit and think when I'm not recording. I will then grab all of this and switch to that. And then down in my handler, I'll paste it in. Okay. So again, notice we're doing exactly what I talked about. We took code that was in the controller and we extracted it into a different class. So now the controller can defer to this job. The artisan command can defer to this job.
So now the controller can defer to this job. The artisan command can defer to this job. The incoming webhook can defer to this job. It's almost like I'm freeing this code from the controller. Okay. Now, just a couple of things. This job is called sendInvoice, not sendLatestInvoice. So with that in mind, maybe we should either feed it the invoice that we are rendering and delivering, or we give it an ID and we track it down. Whatever you want.
and delivering, or we give it an ID and we track it down. Whatever you want. Let's do this. Let's accept my invoice. And then I will assign it. And then down here, I can simply defer to it. All right. And then here, maybe I could update my Invoice class and have something like a savePath on it. But until then, let's do this invoice and I have an ID method on it that does the same
on it. But until then, let's do this Invoice and I have an id method on it that does the same thing. Okay. And actually interpolate and bring it back. Okay. Anything else? We might want to extract this to a method. Maybe this will be the HTML. Because then, think about it, I could inline that variable, browserShot, grab the HTML,
Maybe this will be the HTML. Because then, think about it, I could inline that variable, browser shot, grab the HTML, create the PDF, and then notify the user. And then I always hate that PHPStorm does this. It should just be that. So I'm going to let you take a look at this. Here's what we have. And let's get rid of the comments for now. Yeah. Here's what we got.
We could make that the default maybe. Or we could accept it or do both. Now I can send an invoice to any User. Or if there's a relationship on Invoice, I could remove this entirely and then do something like invoiceOwner notify. That can be useful as well. But yeah, in this case, I don't think I have that, so we will accept User like so. But yeah, that's something we might want to do. Okay. So think about it.
Update controller to dispatch8:37
Okay. So think about it. Now, if I switch back to InvoiceDownloadsController, this logic, like we said, has now been freed, and I can instead defer to my Job. Watch this Job through a queue, which means it's going to be run asynchronously, which is fine. And now, don't forget, we have to give it the invoice. So that would be exactly what we had before, invoice, and then we will feed it here. And then we need the user. But yeah, like I said, I think we can grab the user off the invoice because there is
And then we need the User. But yeah, like I said, I think we can grab the User off the Invoice because there is a connection there between one User and one Invoice. But we don't have that right now. So I will keep it like this. And like I said, we're going to return done. But in real life, it would probably be a redirect. So let's reformat, remove any unused imports, and this is our new Controller. Now before we test out our artisan command, why don't we check the web UI real quick. If we come back to that endpoint, give it a refresh.
Now before we test out our artisan command, why don't we check the web UI real quick. If we come back to that endpoint, give it a refresh. All right. Come back to mailhog, and that is working exactly as it did before. But now we're triggering that logic through a job and through a queue. Okay. So now I'm going to copy this. We can switch back to our artisan command and dispatch that job. And do note these should be imported up here automatically by phpStorm. Okay.
And do note these should be imported up here automatically by phpStorm. Okay. So now to, of course, we don't have a invoiceId in the URI or anything, but I do have a latestInvoice method that will do the exact same thing. So I will stick with that. And now this should work as well. And then let's just update this to our User object and reformat. All right. And this is our artisan command. We ask for the email address.
And this is our artisan command. We ask for the email address. We try to track it down or fail if there is no User, and then we dispatch a job that delivers the latest invoice to the User. All right. Let's give this a shot. php artisan lc sendLatestInvoice. What is their email? hauser@example.com. There we go.
Add model convenience method11:12
There's nothing else to do there. Okay. So to wrap up this episode, yeah, I think I would be happy to see something like User sendLatestInvoice. And yeah, when we do things like this, let me switch over to User and I'll scroll down to the bottom. But yeah, if we had something like sendLatestInvoice, this logic isn't going to reproduce what we had in the controller. It's going to reproduce the job being dispatched. And yeah, that's an important distinction.
It's going to reproduce the job being dispatched. And yeah, that's an important distinction. So let's cut this, return to my User model, and I will paste that in here. And yeah, here I can just replace user with the current user instance. Okay. So let's go back to my artisan command. I can now merge these. sendLatestInvoice. I think so. Next, in my controller, I can do the exact same thing.
I think so. Next, in my controller, I can do the exact same thing. Notice this User sendInvoice, and that ends up being pretty clean as well. But actually, you know what? In this case, we're not necessarily sending the latest invoice. We're sending an invoice. So yeah, actually, I spoke too soon. Maybe we would need to add one more method like sendInvoice. And maybe actually if you call sendInvoice and you don't give it anything, it will default to the latest invoice.
