Introducing Action Classes0:06
All right, so now I'm gonna introduce you to action classes. So I think you like this. Let's go into idea Controller, down to that store action. And yeah, in the last episode we talked about how this was getting a little bit clunky and we can't easily reuse it. So here's what I'm gonna do. I'm gonna take the entire thing here and remove it. And instead, I want you defer to a class that has a name
I'm gonna take the entire thing here and remove it. And instead, I want you defer to a class that has a name that describes the thing I'm doing. And in this case, even if it's as simple as create idea, that's fine. We could even substantiate this and then call a method like handle. But the method can be anything you want. I'm just gonna call it handle, handle the job of creating a new idea.
Creating the Action Class0:42
I'm just gonna call it handle, handle the job of creating a new idea. And you know what? This is effectively an action class. It's just a, it's just a class. And often in the programming world, we like to create classes and assign pattern names to them, but at the end of the day, it's just a class. So let's create an actions directory. It will be within the app folder, it's gonna be an actions folder.
It will be within the app folder, it's gonna be an actions folder. And in this case we have one called Create Idea. Alright, so here is our new layout app, actions Create Idea. Cool. So let's add our file template class, create idea. We're gonna have a handle method, but immediately I can already see, okay, handle the process of creating an idea. Well, I need something to do my job, right? I need the array of attributes.
Well, I need something to do my job, right? I need the array of attributes. So let's make sure we send that through. Let's call a handle method. I can say give me the safely validated attributes, but in this case, I do want to, uh, either accept validated input or if I wanna cast it to an array, I could say all. Okay, so now if I were to switch back and we just did a sanity check here, let's die
Okay, so now if I were to switch back and we just did a sanity check here, let's die and dump the attributes and see how we're doing. So one more time, let's submit it, provide gibberish here, and sure enough, we have all of the data. Perfect. Okay, so now at this point, yeah, all of the logic that lived here can now be placed within our handle method and we'll just have to tweak a few things, paste that in. And yeah, this also gives us the opportunity to clean things up.
And yeah, this also gives us the opportunity to clean things up. We should wrap this within a transaction. Just a couple things like that. So the first thing I see is I would like this class to be usable anywhere, right? Maybe from the command line. Again, maybe AI could reach for this if it needed to as a tool. Um, but now I'm assuming that we are always working
for this if it needed to as a tool. Um, but now I'm assuming that we are always working with the authenticated user. So it's a good assumption, but it may not always be the case. So we have a couple choices here. One option is to accept the user we care about, and then we could delegate like this that works. Uh, but then we would have to do something like this where we now have to pass through auth user.
Uh, but then we would have to do something like this where we now have to pass through auth user. Or by the way, you can also access the user instance off of the request. Uh, and that would work. However, another option, and this is something you should consider, is you can make the user known and then you can default to the authenticated user. And this is kind of nice because it allows for configurability, but you can still
Decoupling from Request3:05
And this is kind of nice because it allows for configurability, but you can still make certain assumptions. So for example, I could say user equals whatever you gave me. Or I will assume that we default to the authenticated user. Um, so that's entirely an option if you want. Okay. Alright. So now moving on to request. Generally this is considered bad form. Uh, we don't want action classes reaching into the request.
Generally this is considered bad form. Uh, we don't want action classes reaching into the request. Uh, ideally they should be entirely decoupled and that's the entire point of them. So with that in mind, I don't wanna do it like this. Instead, I just wanna set up, um, a data variable that contains all of our attributes. So here's one option. I could say collect our attributes and we could use accept or only. So if we wanna be explicit, maybe we should do only
our attributes and we could use accept or only. So if we wanna be explicit, maybe we should do only and say, okay, in order to insert an idea, I want the title, the description, the, the status, and the links. I think that's the only thing we need initially. Okay? And then I can convert that back to an array. It's just a quick way to, to quickly massage these attributes to get what we need. Of course, there's basic PHP array functions too. Okay, so then I could update this,
Of course, there's basic PHP array functions too. Okay, so then I could update this, but I think we can do this instead. Why don't we upload the image first and first? Why don't we check to see if we even have this. So I could say if attributes, image, and we'll default that to false, then and only then, uh, will we update it? And then I will write this immediately. Data image path equals,
And then I will write this immediately. Data image path equals, and this can now be attributes, image. Remember attributes image will be an uploaded file instance, and then we call store on that. Okay? So now if we take that approach, we can make a single query to create the idea with the image already included. That way I don't have to do this down here, and it cleans it up a little bit.
That way I don't have to do this down here, and it cleans it up a little bit. Okay? Next we create the idea and what's the issue? Undefined method. Ah, it's not right. Maybe, hmm, let's be explicit. Um, if you ever run into a situation like this where it's telling you like maybe this isn't valid, you can just, uh, clearly define the variable for a user and now you'll see that go away. Yeah, sometimes you have to do that. Okay?
and now you'll see that go away. Yeah, sometimes you have to do that. Okay? Then finally we create the steps. Okay? So now, yep, we're not using the request anymore. This would be attributes, steps, but we can assume an empty array in that case just to save a couple keystrokes, and that's done. And then what we might wanna do, just for clarity, you don't have to do this, uh, but just for clarity, is we might want
you don't have to do this, uh, but just for clarity, is we might want to extract this into its own variable that then allows us to do something like this. Yeah, sometimes I will inline it, sometimes I won't. It's just kind of a, I dunno, it's just sort of a, a feeling you have. All right. So now the final step, I think maybe I've said final twice now, is I should probably wrap this within a transaction.
Wrapping in Transaction6:02
I think maybe I've said final twice now, is I should probably wrap this within a transaction. As a good rule of thumb, whenever you're doing a bunch of different, uh, database operations, it's possible that something will go wrong. And in these cases, we don't want our database to be in an inconsistent state. We just wanna roll everything back. So with that in mind, here's what we might do. I'll wrap this within a call to our DB facade.
So with that in mind, here's what we might do. I'll wrap this within a call to our DB facade. And I can use transaction. This is something that Laravel of course provides. I can bring it in. And then let's just make sure that we use whatever we need here, in this case, the user, the data. And I think that's it. Alright, so now, yeah, if we want, we could extract a method if we want. Uh, but for now, sometimes it's fine
Testing and Container Injection6:41
we could extract a method if we want. Uh, but for now, sometimes it's fine to just keep it in line as we see here. Alright, so now here's the cool thing. Don't forget that we have a test for create idea. If I load the page and I fill out the form, then I expect this to be inserted. So even though I've made a, a significant change here, we've now delegated to a dedicated action class. Well, I still have the test to ensure
we've now delegated to a dedicated action class. Well, I still have the test to ensure that I did everything, uh, properly. So let's give it a shot pest on test browser, create idea test. We run it and oh, it passes. Awesome. I actually didn't think that was gonna happen, so that's really good. So let's go into idea controller down the store, and yet to sanity check if we un commented
So let's go into idea controller down the store, and yet to sanity check if we un commented and it's gonna fail, uh, and it does as we'd expect. Okay? So now you can clean things up, you can refactor with the assurance that the whole time the test is gonna let you know if you made a mistake. So for example, one thing I might do instead is rather than instantiating the action directly, I'm gonna ask for it from the container. So if I type this, Laravel is smart enough to see, um,
for it from the container. So if I type this, Laravel is smart enough to see, um, okay, you want an instance of this and it's going to inspect it, it will determine whether or not it can instantiate it for you, and then it will pass you a new instance of that. So now this could change entirely to action handle, handle the action of creating an idea. So I rerun my test, it goes and it's green. So everything passed. Now maybe you decide you don't wanna
So I rerun my test, it goes and it's green. So everything passed. Now maybe you decide you don't wanna use safe, but validated instead. Um, we don't, but just to show you that you can make changes and if anything went wrong, it would let you know. Cool, right? Let's go into our action class. This is what we currently have. Still a little bit of room for improvement, but um, now at least I can trigger this action from anywhere in my code base.
but um, now at least I can trigger this action from anywhere in my code base. So let's bring up the terminal. I'm gonna wrap up by saying composer run format. All right? And we can see three files change. Yeah, just added some types that I might have missed. So now I'm deciding if I want to show you something, it's a little bit more advanced, but I think it's cool. Let's do it. Let's do it. So right here we were asking for a user, right?
Injecting Current User8:58
Let's do it. Let's do it. So right here we were asking for a user, right? And then to allow for a clean API, we said, well, if you don't give us one, let's just assume that you want the authenticated user. It's a, it's a safe assumption. Okay, well this works, but there's actually a cleaner way we could do this and I'll show you how. Let's define the constructor. And I want the user up here.
Let's define the constructor. And I want the user up here. So you gotta give us a user and then we'll make it available to the class. Okay? So now right here, you're not gonna give it with the handle method. So we won't do this. And then down here, we don't need to ask for it. Instead, I will just reach for the user. And now here is the secret sauce.
Instead, I will just reach for the user. And now here is the secret sauce. So we are now saying, in order to instantiate this class, you need to give us the user who is responsible for creating the idea, right? However, I can use a PHP attribute. And if you're, if you're new to these, it's a little confusing at first you use a pound and then two brackets, and then you reference the attribute name.
and then two brackets, and then you reference the attribute name. And as it turns out, Laravel has one for us called current user. And notice the full path I pulled in there. Okay? So now this is our way of saying, yes, I expect a user, but I want Laravel to inject the currently authenticated user as part of that. Okay? So now I'll go back to idea controller. Let's rerun our test and we still get green
Okay? So now I'll go back to idea controller. Let's rerun our test and we still get green because of that attribute. And again, I just wanna prove this to you. If we didn't have this, it's gonna fail because, well, it expects a user and Laravel is trying to create the class for you and it doesn't know what user it should provide. So I think we're gonna get an issue and we do. Cool. So let's be explicit here.
So I think we're gonna get an issue and we do. Cool. So let's be explicit here. Give us the currently authenticated user and then we will work with that. But it still can be, uh, swapped out with anyone we want. That is what I wanna do. Alright, so this is where we're gonna leave it for now. Our store action is now significantly simpler. We declare our form request, we declare an action, we call handle on that action, and then we redirect.
We declare our form request, we declare an action, we call handle on that action, and then we redirect. We're done.
