مرور استخراج رفتار به Actionها0:00
Over the last few lessons, we've created a very simple checker flow, and we've also refactored it quite a bit. On this lesson, we're going to continue refactoring this code, while being respectful of our boundaries. To start things off, let's go back to our tasks, and make sure that they still pass. Okay, we agree, that means we're good to go, and continue refactoring this. Looking at this controller, the first thing we can see is that we have quite a lot of business logic here.
Extract Purchase Action0:28
Looking at this controller, the first thing we can see is that we have quite a lot of business logic here. We are instantiating a payment provider, we're charging the credit card, we're creating an order, we're creating order lines, and we're also creating a payment. And actually, we are also decreasing the stock of the products. Let's begin by extracting this to its own class, so we isolate all of the behavior in a separate place, and not on the controller. I'm going to create a directory called actions within the order modules.
and not on the controller. I'm going to create a directory called actions within the order module. Actions are a very popular pattern in the Laravel ecosystem, and actions are nothing more than self-handling commands. So, if you search for actions in the literature, you might not find a lot, but if you search for the command pattern, you're going to find related articles. Alright, I'm going to create a class called, for now at least, PurchaseItems, and this is where we're going to encapsulate all of that behavior.
PurchaseItem, and this is where we're going to encapsulate all of that behavior. As you can see, we need a couple of things to create a purchase. We need the payment provider, we need the token, and we need a collection of the items that are meant to be purchased. So, on the PurchaseItem class, let's create a method called Hendo. This is also pretty standard. This is what you're going to find on jobs. Again, if you're in the Laravel ecosystem, in actions, and also quite commonly in commands.
Again, if you're in the Laravel ecosystem, in actions, and also quite commonly in commands. On this action, we're going to accept a few things. First, we're going to accept a cartItemCollection, since we need to know which items are meant to be purchased. We're going to accept an instance of the PayBuddy payment provider, and we're also going to accept a paymentToken. For now, this is going to return void. Let's go ahead and copy all of this. There we go.
Let's go ahead and copy all of this. There we go. I actually want to maintain the cart items, but I'm going to copy almost everything else. We can inject this dependency on the constructor, so we can say protected $purchaseItems. There we go. Now, let's just call this right here. We want to Hendo. We want to pass a collection of cart items.
We want to Hendo. We want to pass a collection of cart items. We want to pass an instance of PayBuddy, and we also want to pass the payment token. There we go. Now, we're not going to need this. We are going to need this, so I'm going to copy it. There we go. Let's go to the PurchaseItems class, and let's just paste what we copied.
Refactor Action Inputs2:44
Let's go to the PurchaseItems class, and let's just paste what we copied. Okay, let's see what do we have to refactor here. First, we have to replace this with PaymentProvider. We don't have a request object anymore. We are receiving the token as an argument, so we can pass it here. Let's calculate the order totalCents, which is easy with the cartItemCollection, and then at the same end, we want it to be modularization.
which is easy with the cartItem collection, and then at the same end, we want it to be modularization. Let's import the classes that we need to import, since we've just placed this on a separate place. We need to import Order as well. Again, we don't have a Request object anymore, so we're also going to have to receive this as an argument. Let's go here and add expect an integer called UserID, and then we can replace this right here. There we go.
and then we can replace this right here. There we go. Let's rename this with Items. We have to inject the ProductStockManager as well. It's a dependency. Since this is going to be injected by Laravel's container, we can just create a constructor and pass it within the constructor. There we go. All right, I think that's pretty much it.
There we go. All right, I think that's pretty much it. We might have to... Okay, we also have a UserID right here, so let's replace that with what we're getting on the methods body, and then on our CheckoutController, we're missing a parameter. So let's format this just to make it easier to read, and now let's pass the UserID. All right, so as you can see, now the controller is basically the place
All right, so as you can see, now the controller is basically the place where you organize things that are needed by the domain to execute an action, and we just defer the call to a separate object. Let's run a task and see what happens. Okay, they're still passing. That's good. Now, one thing that's bothering me, too, is we're throwing this very generic...
Introduce Payment Exception4:35
Now, one thing that's bothering me, too, is we're throwing this very generic... First, we're catching this very generic Exception, right, which is okay for now, but we're also throwing a ValidationException from within this class, which really is meant to execute business actions. To me, this should be thrown on the controller, not on this purchaseItems action. Ideally, we would have an abstraction communicating
not on this purchase items action. Ideally, we would have an abstraction communicating to the PayBuddy SDK, but for now, let's simply throw one of our own exceptions. Let's say something like PaymentFailedException, and let's call a set method called invalidToken, or even better, to be more descriptive, do to invalidToken. We have to create this class, put it on Exceptions.
We have to create this class, put it on exceptions. We want this to extend RuntimeException, and now we also need to create this method, and it's just going to return a new instance of itself where the message saying the given paymentToken is invalid. There we go. Perfect. Now we can throw this, and we can instead catch this on the controller.
Now we can throw this, and we can instead catch this on the controller. Let's go to our CheckoutController. We want to have a try-catch block here. We're going to catch the PaymentFailedException, and then we want to throw a ValidationException, which is the controller's responsibility. Let's rerun our tasks. Okay, they're still passing, and now we can finally return something from this class.
Return Order Result6:10
Okay, they're still passing, and now we can finally return something from this class. For now, let's return the order, and then we also have to update the return type to say order. There we go. Okay, that's much better. Now we have this encapsulated on an action that's solely responsible for executing the purchase of a collection of items, of cart items.
that's solely responsible for executing the purchase of a collection of items, of cart items, which are products at the end of the day. It is also responsible for decreasing the stock, creating the related objects, etc., etc. At this point, I think it's also reasonable for us to return a better response here, maybe something including the order URL so we can redirect the user. Let's go to our task,
Add Order URL Response6:50
so we can redirect the User. Let's go to our task, and we can also verify that we returned some JSON. So we can say assertJson, and let's keep it simple. Let's say orderUrl. Let's get the order first because we're going to need it, and we're going to say orderUrl. On this URL method, we're going to return the orderUrl, and to do that, we need to first create a route. So let's create a route called get.
and to do that, we need to first create a route. So let's create a route called get. We expect an order with an id, and we can use order here. For now, let's skip this on a closure just to keep it simple, and we're just going to return the order. So this is going to return some JSON. We can name it order.show, and then on our order model, first, let's create this called URL. It's going to return a string, and then we can call the route right here,
It's going to return a string, and then we can call the route right here, orders.show, and we can pass the object. There we go. So we're using implicit route model binding to fill the necessary arguments for this route, and having this as a method within the model is just something I've done for a while, and I've always liked it. It makes it very easy to read, and it just acts as some sort of helper method.
It makes it very easy to read, and it just acts as some sort of helper method. So let's go back to our tasks. This makes sense. Let's run this and see what happens. The JSON is obviously invalid, so let's go to our controller and return something that makes sense. We have the order. Actually, we do not have the order,
We have the order. Actually, we do not have the order, so let's go here and store this in a variable. There we go. Okay, that's pretty much it for this lesson. Very quick. All right, let me just get rid of this empty line, and we're done with this lesson. See you on the next one. Bye-bye.
See you on the next one. Bye-bye.
