Improving Code Intent0:00
Hey, welcome back. I know we've been spending quite a lot of time on this class. Don't worry about it. We're not going to spend that much longer now. And I think this looks good now, but I still think we could make it a little bit better. In my opinion, one of the most tricky and one of the most important things in software engineering is passing intent within your code. So let's take a look at this. At this and at this, we're manipulating data quite frequently, and it doesn't have clear intent. This is a very simplified example, but within real life applications, I'm sure you've seen pieces of code where you manipulate a lot of data. It works, but it's not clear.
but within real life applications, I'm sure you've seen pieces of code where you manipulate a lot of data. It works, but it's not clear as to what it's meant to do. On this lesson, let's take a look at how I would personally write this. First, let's run a task to make sure that we can refactor this, and we can. They're passing. First of all, we are instantiating this Order model. We're actually persistent at first. Then we are creating order lines for each one of the current items, and that's pretty much it. Then we create the Payment. Now, the first thing here is, sure, we have a transaction here, so you wouldn't have an Order without a Payment if it happened to fail.
here is, sure, we have a transaction here, so you wouldn't have an Order without a Payment if it happened to fail. But at this point in time, the Order is not really complete. It is spending. It doesn't have a Payment attached to it yet. It also doesn't have orderLines. So at this point, it is an invalid object. I would start this with, for example, orderStart for User. We're basically starting an Order. It isn't complete yet, and then we could pass the userId. So I'm going to comment this just so we have a reference to see what it was. Now, within the Order model, I'm going to create a static method
Adding Order Start Method1:36
So I'm going to comment this just so we have a reference to see what it was. Now, within the Order model, I'm going to create a static method called start for User. We expect a userId, and we return an instance of the class itself. So we can say self::make(). We want to create a new model. We don't want to persist it yet. We can say $userId equals $userId, and the status could be pending. Let's make this a const for now. Yes, it could be an enum. I don't think we need that now. So let's say public const pending equals spending. And now we can
Encapsulating Order Line Creation2:08
So let's say public const pending equals spending. And now we can say that this order is spending at this moment in time. So we've started this order. Right here where we add the order line, we could encapsulate this within the Order model as well. We could say something like addLinesFromCartItems, and then we could pass a cartCollection like this. So let's copy this for now, and then we can get rid of this line and create this method. addLinesFromCartItems. We expect
and create this method. Add lines from cart items. We expect cart item collection, and we do not return anything. So this block of code actually persists an order line. This wouldn't work because this order does not have an ID yet. It hasn't persisted. So let's see how we can refactor this. First, let's add a doc block to let Static Analysis and PhpStorm know that this is composed of cart items. Let's import this class. And now we can go through each item and do what we're supposed to do. We have the lines relationship, right? This one right here. And we can
go through each item and do what we're supposed to do. We have the lines relationship, right? This one right here. And we can push an object to it. So we can do something like lines.push. We're going to push an OrderLine. We can also use the make method to instantiate an object without persisting it. And then we can do the exact same thing. productID is going to be item.productID. productPriceCents is item.productPriceCents. And then quantity is item.quantity. There we go. So let's get rid of this. Now, one thing we're missing, though, is
item quantity. There we go. So let's get rid of this. Now, one thing we're missing, though, is right here we were passing total and cents beforehand. Now we're not. But we can calculate the total based on the line items. So we can do something like $total and $cents equals this lines. We want to sum them. We're passing a closure that's composed of an orderLine. And we want to return the productPrice and cents. The reason I'm not using the cartItem collection to calculate this is because you could call this method multiple times. Another option would be using the cart
The reason I'm not using the cartItem collection to calculate this is because you could call this method multiple times. Another option would be using the cartItem collection to calculate this and checking very early if this order already had items. And if it did, we would fail this operation. For example, if this line is not empty, so we already have orderLines added, we could throw a custom exception here. That would be an option as well. Let's get rid of this. So we're adding the lines from the cartItems. We're also starting the order for the user. Now, the problem is this isn't going to work. Let's run a test.
Persisting Order with Fulfill4:48
the order for the User. Now, the problem is this isn't going to work. Let's run a test. Okay. Calling URL on no. We got a 500. So create payment for order. Argument one is orderId of type M. We're passing no. And the reason is this property does not yet exist. We haven't persisted the order yet. So we can call a method fulfill at this point to actually fulfill the order. Let's go here add a method called fulfill. It doesn't have to return anything. And this is going to do a couple of things.
add a method called fulfill. It doesn't have to return anything. And this is going to do a couple of things. First, it's going to update the status to it already has the order lines. It doesn't need anything else. Let's add this const as well. Complete it. Now, we need to save this object and we also need to save its order lines. So we can say lines. We want to save many. And then we can just pass the lines we've already added in memory. Let's run our codes. Let's run our tests actually and pray this passes. Okay. They're passing. So now
Let's run our codes. Let's run our tests actually and pray this passes. Okay. They're passing. So now we have a code that communicates intent better. We're starting an Order. We're adding lines and then we're fulfilling the Order. If we had more SAPs, this would be much more concise as well. Let's get rid of this. So you could actually have this method return an actual persistent instance of an Order. I don't really see why since it isn't complete yet and it would be missing the order lines. So if you were to call this at some other place, you could have an invalid Order with this since it's only in memory.
Enforcing Order Line Validation6:24
So if you were to call this at some other place, you could have an invalid order with this since it's only in memory. And then we're fulfilling the order. You can ensure that you always have an Order with OrderLines. You could also go a little bit further and say that if this Order does not have OrderLines, for example, if the number of OrderLines is empty, if you don't have any OrderLine, you could throw an exception as well. For example, OrderMissingOrderLinesException. And let's put this within exceptions.
order lines exception. And let's put this within exceptions. We want this to extend RuntimeException and get rid of this constructor. With this, you guarantee that you can only fulfill an order if it has orderLines. So you're guaranteeing a business aspect. It doesn't make sense for you to create an Order without orderLines and you're enforcing this through those lines really. And since we have that behavior encapsulated within the method, we can guarantee that it's going to happen. Alright, let's rerun our task to make sure everything still works. It works. And now I think we've
guarantee that it's going to happen. Alright, let's rerun our task to make sure everything still works. It works. And now I think we've refactored this quite enough for what we wanted. Just to wrap things up, let's add a doc block saying this can throw an exception. This is important. It throws an OrderMissingOrderLineException. And our createPaymentForOrder also throws an exception, the PaymentFailException. So let's add that as a doc block. There we go. We don't need those because we already typed a hint at those.
a doc block. There we go. We don't need those because we already typed a hint at those at the method itself. So let's get rid of those. There we go. Let's rerun our task. We didn't change any code, so they're obviously going to pass. And now we're good to go. Alright, I think this class looks pretty good. It's pretty clear in what it does. We're starting an Order, we're adding order lines, we're fulfilling it, we're decreasing the stock for the products we just purchased, and then we're triggering a payment for that order all within the transaction. Now since we only use this value right here, we only use the orderTotal in cents.
Final Cleanup and Simplification8:34
a payment for that order all within the transaction. Now since we only use this value right here, we only use the orderTotal in cents. At this point, we can also get rid of this. Let's remove this from the closure. And right here, since we have the items, we can just call total in cents. And to make this easier to read, let's add some line breaks as well. Alright, I think that looks pretty good. Let's wrap this last one right here, and I'll see you guys on the next one. Bye bye.
on the next one. Bye bye.
