مرور اشیاء غنی و کیسههای داده0:00
Hey there. Welcome back. On the last lesson, we spoke a little bit about pseudo strips and we learned that when we have structured data, that is when we know what the data shape looks like, we can use objects instead of arrays to gain a couple of benefits. First, we can enforce the data shape in the way that we want, not only when generating the data, not only when instantiating that object,
that we want, not only when generating the data, not only when instantiating that object, but most importantly when consuming it. Our consumers know what the data looks like and it is enforced by php itself. We can enforce types and to me one of the most important benefits, we give something a name, it becomes a concept within our domain and we were doing that with an Invoice interest simulation.
Refactoring the Class0:40
it becomes a concept within our domain and we were doing that with an invoice interest simulation. So on this lesson we're gonna jump into the code and we're gonna look at some improvements we could make to the class and maybe even add some sprinkle of immutability. So let's jump into the code. Okay, this is where we left off on the last lesson. First, let's rerun our tasks to make sure that everything's okay and that is good.
First, let's rerun our tasks to make sure that everything's okay and that is good. And now let's see what improvements we could make to this class. Well, the first thing that I'm seeing is that this object is a bag of data and sometimes that is exactly what you want, especially when you're transferring data between context. Those are what we call data transfer objects. But in this particular example, I think we could limit
Those are what we call data transfer objects. But in this particular example, I think we could limit how much data it takes. And then first some of the what is currently data as behavior. Let's, let's explore that a little bit. Looking at this structure, we have a lot of properties here. Which properties are absolutely necessary for us to be able to calculate the interest for invoice and what data could be methods instead,
to calculate the interest for invoice and what data could be methods instead, what could be inferred? Well, monthly interest rate is obviously needed, the dates as well, but everything else elapses total interest in total interest percentage. Those could be methods. So let's refactor this a little bit. I'm going to get rid of this properties, actually, I'm gonna keep them here for now. Just so we know which methods we're adding.
Turning Data Into Methods2:08
I'm gonna keep them here for now. Just so we know which methods we're adding. Let's add a method called elapsedPhase. This is going to return a true and we know this is the invoiceDueDate, the difference in days to the referenceDate. What about totalInterest? Well, let's go back to our controller where we are currently calculating this and just copy the logic we have.
where we are currently calculating this and just copy the logic we have. I'm gonna copy all of this. I'm gonna paste this here and the first thing we need is the dailyInterestRate. So I'm actually gonna add this as a private function here. So let's say monthlyInterestRate or dailyInterestRate. This is going to return a float and this is going to be the monthlyInterestRate divided by 30.
and this is going to be the monthly interest rate divided by 30. So we can get rid of this. The total interest percentage, this is also going to be a method because we return that on the object. It is part of the data. So we're gonna say totalInterestPercentage. This is also going to return to float and this is going to be elapsedPhase times dailyInterestRate.
and this is going to be elapsed phase times daily interestRate. There we go. Cool. And then all that's left is totalInterest and totalInterest is super easy. This is also going to return to float and this is going to be the, okay, it seems we need one more data point here, which is the invoiceTotal. So let's add that. We're gonna say invoiceTotal,
which is the invoice total. So let's add that We're gonna say invoiceTotal, and this is going to be the invoiceTotal times the totalInterestPercentage for that period. And now we can obviously get rid of this. Now let's get rid of the ones we did not want. We don't need this. And let's go back to our tour method. So my ID is already complaining about some things. Elapse states, this is not a method totalInterest, this is now a method and totalInterestPercentage.
Updating Tests and Controller3:45
Elapse states, this is not a method totalInterest, this is now a method and totalInterestPercentage. This is also method. If we run our tasks, they should fail. They are failing. So let's go back to our controller. Let's refactor how we are instantiating this object. Well those do not exist anymore. And then the invoiceTotal is gonna be invoiceTotalAmount and we can get rid of this and we can get rid of this. Let's rerun a task and see what happens. Still failing.
Debugging With Exception Handling4:11
and we can get rid of this. Let's rerun a task and see what happens. Still failing. So the notification is not being sent. Okay, let's take a quick break. This is not relevant to this course, but it's a good opportunity to talk about something that might help you when debugging your Laravel applications. When running tasks. Sometimes your application is going to throw an exception
When running tasks. Sometimes your application is going to throw an exception and that exception is going to be caught by VOS exception handler and you might not have a clear picture on your tasks of what's going on. So take a look at this. We know that this notification is not being sent, so this assertion is failing and if we go into our controller and we try to dump
so this assertion is failing and if we go into our controller and we try to dump and d this collection, which happens before we even instantiate that notification, we get the same error. So that indicates to me there's something wrong. We're probably throwing error that's not being exposed to us. What you can do and that happens is go to your task. And this is especially relevant if you're
What you can do and that happens is go to your task. And this is especially relevant if you're running a TTP tasks. And before I do this, I'm just gonna show you what we have as the response. So if we do body on the response, you can see that we have a message. So we have an error, it is just not being thrown and therefore it doesn't show up in the test. What you can do in those cases is you can go to the start
therefore it doesn't show up in the test. What you can do in those cases is you can go to the start of your task and you can say without exception handling, this is going to disable the exception handling, which often is okay, but if you're for example, tasking validation errors or if you have an exception that renders as a validation error for example, or has a custom render implementation since it's not going to be caught
or has a custom render um, implementation since it's not going to be caught with the accepted handler, you won't see that. Let's run this now and now we get an actual error. So invoiceTotal must be of type float and we got a string. So for now, all we're gonna do is we know this is a monetary value, it is a numeric value. We're just gonna cast this as a float. Let's see if that fixes it. And our task is back to green and we can get rid of this.
Restricting Instantiation Patterns6:05
Let's see if that fixes it. And our task is back to green and we can get rid of this. So we've have refactored this a little bit. We could rid of some properties we did not need and now we have something a little bit easier to digest. Here's a thought. We have a pretty generic object, right? We take primitives and we take two Carbon instances. This object could be instantiated anywhere within the application. And in this example it's pretty simple.
anywhere within the application. And in this example it's pretty simple. We know exactly where it is being instant shaded, it's pretty specific, but on your application it might not be. What we could do here is limit how this object can be instant shaded. A lot of people do not know about this, but we could make this structure private. And what a private constructor means is that the only one
but we could make this structure private. And what a private constructor means is that the only one who can accentuate this class is the class itself from a static context for example. So if we try to run our tasks, it is going to fail and let's add that guy again so we can see the error. And you can see that construct is private, so we cannot call it, here's what it could do. We know that we want to generate this from an invoice. It could be generated from anything.
We know that we want to generate this from an Invoice. It could be generated from anything and maybe in the future we would like to generate this from different places, um, not necessarily from an Invoice, but for now we know we want an Invoice. What we could do is we could have a static structure. This is how we typically call those. So we would do public static function and we could saveFromInvoice().
So we would do public static function and we could save from invoice. We would expect an invoice. So since we have invoice type hinted here, we are guaranteed to always get an invoice here. It's always going to be consistent and we can capture the dueDate from the invoice. So all we would need is the monthlyInterestRate and the referenceDate. And this is going to return an instance of South.
and the reference date. And this is going to return an instance of South. So we can just do south and monthlyInterestRate. We're gonna do invoiceDueDate, referenceDate, and the invoiceTotal. What does that give us? Well, the guarantees that there's only one way to instantiate this object and it must be done through an Invoice. So you cannot just arbitrarily instantiate this object. You cannot just do new InvoiceInterestSimulation from
So you cannot just arbitrarily instantiate this object. You cannot just do new InvoiceInterestSimulation from whatever new App you have to do it with an Invoice. Our tests are going to fail, so let's adjust that. We're gonna say from Invoice, we no longer care about this and we care about the Invoice. Let's sort the arguments. There we go. And let's rerun this Invoice. So must be a type float through. Okay, same problem. So let's just cast this to a string.
So must be a type float through. Okay, same problem. So let's just cast this to a string. Cool, there we go. Another benefit of this is that this method also acts as a boundary. So if you were instantiating this object from multiple places with this with a static structure, you have one single place to instantiate it. All your calls go through this method and it is a little bit more predictable. Now the question we always ask, should you do this?
and it is a little bit more predictable. Now the question we always ask, should you do this? Well, it depends as most things in software development, it really depends. If you have a real simple object, if you trust that you're going to be able to keep track of where it is being instantiated and it's stable enough, this isn't needed. This is never needed, you're never gonna need this. It is an option. I think in a lot
This is never needed, you're never gonna need this. It is an option. I think in a lot of times it does make sense when you have something that can only be instantiated through a very specific way, especially when you have more generic objects that could be instantiated at anywhere. This is super helpful. Do you need to always use this? Do you need to even use this? No, this is just an option. Cool, so we got something going. Another thing that you could do here is if this object
Coupling to Invoice Object9:45
Cool, so we got something going. Another thing that you could do here is if this object and you should think about something in your own application, not about this specific one, but if this object were to be coupled to Invoice, and in this case it is, right, it's an Invoice interest simulation, at least for now. Maybe in the future it becomes something a little bit more generic, but for now it is an Invoice interest simulation. We could, instead of having the due date
generic, but for now it is an invoice interest simulation. We could, instead of having the due date and having the total, we could just have a private Invoice here, right? And that would make things a little bit simpler. So in our case, since we're using this static constructor, we don't even need to refactor the consumers. We can just go here and we could say that this is going to be the invoice we passed and there we go. Now obviously we're gonna have to refactor a little bit.
to be the invoice we passed and there we go. Now obviously we're gonna have to refactor a little bit. So invoiceDueDate becomes invoiceDueDate and invoiceTotal becomes invoiceTotalAmount. And we wanna cast this to a float. There we go. And here as well. So dueDate, if we run the task, they should pass. Let's try it. No monthlyInterestRate must be of type float Carbon mutable given. Okay, I think we have, yeah, this is incorrect.
of type float carbon mutable given. Okay, I think we have, yeah, this is incorrect. This should be monthlyInterestRate and this should be referenceDate. There we go. Let's try this now. There we go. It's surpassing. Now we're coupled to an Invoice, but I think that's okay because in this specific example, an Invoice interest simulation is side to an Invoice. Let's say that in the future we want to refactor this, we want this object to be a little bit more generic.
Let's say that in the future we want to refactor this, we want this object to be a little bit more generic. Not only apply to invoices. Well, since we have this static constructor, it's pretty easy. We wanna have to refactor any of the consumers. We could just go here and say, well now instead of an invoice, I want this new shiny object, right? And as long as I can get this new shiny object from an invoice, I could say new shiny object.
And as long as I can get this new shiny object from an invoice, I could say new shiny object from invoice or something. As long as we can somehow instant shape this new shiny object, that's okay. It is still gonna work. Since this property is private, it was private already, it means that it was not accessible by external actors, which means it was only being used internally. So everything that you need to refactor
used internally. So everything that you need to refactor is internal to this class. And this is also why sometimes it is important to be a little bit careful with visibility when it comes to properties. If this were public and consumers were using it, then yes, that wouldn't work. It would be a breaking change. But since this is internal, we just had
It would be a breaking change. But since this is internal, we just had to refactor this specific class. Pretty, pretty easy. With that said, let's go back to having an invoice here. Now, going back to the static structure, would I always use this always is a very, very strong word. And like I said earlier, it really depends. I always say this in all of my courses, but I want to inforce this.
I always say this in all of my courses, but I want to inforce this. What I'm teaching are, let's think about a language. You know a lot of words in a language, but you don't use all of them every time. There are common words you use in your day to day, and there are some words that are, you know, reserved for some specific usage. The same with recipes. Even if you know a bunch of recipes on your day-to-day,
The same with recipes. Even if you know a bunch of recipes on your day-to-day, you're gonna cook the same stuff. It's the same with knowing different ways to code. So what I want to give you are options. Maybe you already knew some of them, maybe you already knew all of them, but this is all about options, about stumbling upon situation and going, oh, I know how to solve this,
about stumbling upon situation and going, oh, I know how to solve this, or I know a good solution for this and this is what it's all about. In practical terms, when would I use this? If I have an object that I can picture getting refactor in the future or that I can picture getting more complex in the future, I would probably use a study constructed. It is more predictable
I would probably use a study constructed. It is more predictable and it is a boundary that will allow me to refactor this a little bit easier. If this is like this Invoice interest simulation, a very simple object, this one is already very coupled to an Invoice and I don't see this getting much more complex than that, then I would probably skip it. In my opinion, and I don't want to turn this video into a different subject,
In my opinion, and I don't want to turn this video into a different subject, but I think this fits this lesson quite well. In my opinion, one of the most important things you should do, you could do when learning new techniques and ways to program is to try them out. So try this out, see whether it fits your problem, your project. If it doesn't, it's very easy to undo this early on. Even if it had multiple static structures.
If it doesn't, it's very easy to undo this early on. Even if it had multiple static structures and they were being used in multiple places, it's easy to refactor them into just using the normal constructor. The other way around though, when you have an object being instantiated in multiple places in different ways and whatnot, that's harder. Introducing static instructors than is a little bit harder. So try it out. Everything on the scores, give it a try. If you don't like it, undo it. And that's fine.
So try it out. Everything on the scores, give it a try. If you don't like it, undo it. And that's fine. Maybe at some point you will find a problem. We're gonna go, oh, okay. The thing that Matto said, I think it fits here. You don't have to use all of them. You're definitely, and you shouldn't use all of them all the time. With that said, I think this was enough for this lesson. I hope you guys enjoyed it and I'll see you in the next one.
With that said, I think this was enough for this lesson. I hope you guys enjoyed it and I'll see you in the next one. Bye-bye.
