Creating Immutable Objects0:00
Hey there. Welcome back. On the last lessons, we have spoken about objects as data bags, private structures, and we've also added some behavior to one of our objects. On this lesson, we're finally gonna come back to immutability. We've spoken about immutable objects very early on, but we have not spoken. And how do we create our own immutable objects,
but we have not spoken. And how do we create our own immutable objects, or rather, how do we make it so our existing objects become immutable? And this is what we're gonna check out on this lesson. So without further ado, let's zoom into the lesson. Here we go. We are still working with our invoice interest simulation example. I've just added one very, very silly feature. Let's look into this block of code.
Bug From Mutation0:46
I've just added one very, very silly feature. Let's look into this block of code. So for each simulation, we notify the team with a new InterestSimulationIssuedNotification. This already existed. What I've added was this block of code. So what we're doing is we're also sending a few extra options of what a simulation would look like with higher interest. We want to make as much money as possible,
with higher interest. We want to make as much money as possible, therefore we want to see what that will look like with a higher interest rate. So if the user were to pass a 2% monthly interest rate on this notification, we would send 2%, and then for each iteration we would add one, two, and 3% extra. On top of those 2%, that's what we want to happen.
and 3% extra. On top of those 2%, that's what we want to happen. So on the first iteration, we would send 3%, that is two plus one, then 4%, and finally 5%. Now, I want you to stop this video and see if you can spot the bug on this code. It is very silly, and by now you have probably spotted it. Well, the problem here is that for each iteration, we are mutating this property on this object, and we're adding an extra 0.01 times i, which is going
we are mutating this property on this object, and we're adding an extra 0.01 times I, which is going to be one through three. So on the first iteration, yes, we're gonna go to three. On the second iteration though, we're gonna go to five. And on the third iteration, we're gonna go to eight. I've added some tasks for this, and you're gonna notice I've commented out most of the assertions. But let's run this. So if I just run the basic assertions,
most of the assertions. But let's run this. So if I just run the basic assertions, making sure that a notification was sent and that the request returned to 200, that is going to pass. Now, if I just add this assertion rate here to make sure that we got 0.02 back from the API response, you're gonna notice that we got 0.08 in stat. Of course, if I add these assertions back, they're also not going to pass.
Fixing Bug by Cloning2:33
Of course, if I add these assertions back, they're also not going to pass. Notice that now we're making sure we send a notification with 0.02 and that we send a notification with 0.03. So let's run this. And they're also not going to pass pretty straightforward bug, right? It's very easy to fix this. Here's how I'm gonna fix it the easy way. First, I'm gonna say new Simulation, and we're gonna clone this object.
First, I'm gonna say newSimulation, and we're gonna clone this object. And then I'm gonna say newSimulation, monthlyInterestRate. We're gonna use the existenceSimulation as the baseline, and then we're gonna add 0.01 times i, and then we're just going to replace this with newSimulation. Let's run test, and we are back to green. However, our object is still immutable.
Let's run test, and we are back to green. However, our object is still immutable. Sure, we have a fix in place. We're just cloning the object, but the object is still immutable and we want to make it immutable, at least for this lesson. So let's do this. Let's go back to having that simulation object. Let's run a test and they should fail. There we go. And here's what we're gonna do.
Using Readonly Properties3:34
Let's run a test and they should fail. There we go. And here's what we're gonna do. We're gonna go to Invoice InterestSimulation. We're gonna go to our monthlyInterestRate property, and we're gonna say we want this to be read only. That's a key word, read only. That's all. Let's run a tests. And now we get a different error. php saying that it cannot modify a read-only property. That's it. If you have a read-only property, once the object has been instantiated,
That's it. If you have a read-only property, once the object has been instantiated, you cannot override it regardless of the circumstances. So here's what we're gonna do. We're gonna go to our controller and we're gonna add a method to the simulation object. Let's add a line break here. And instead of mutating this property, I'm gonna say that I want to add interest. And the amount of interest I want to add is 0.01 times y.
that I want to add interest. And the amount of interest I want to add is 0.01 times Y. We can get rid of this, and I'm going to add this method with read only objects. The only way you can mutate an object is by non-muting it, as instead by instantiating the class again and having a new object with the new value you want. And that's what we're gonna do here. So we're gonna take an interest and we're gonna return an instance of itself.
So we're gonna take an interest and we're gonna return an instance of itself. So let's say return self. Thank you, hy. So we are returning a new instance of its health, same invoice, same referenceDate, but now we are increasing the monthly interestRate. So we're still using the current monthly interest as a baseline, but we're adding some interest on top of that. Now let's rerun our tests.
top of that. Now let's rerun our tests and their back to green with an immutable property. Now we can add the readonly keyword to properties, but we can also add it to the class itself. And when we add it to the class itself, we're telling PHP that all of the properties are immutable. The only way to change any of them is by instantiating a new class. So let's rerun this. We are disagreeing. That's great.
Immutable Dependencies Caveat5:30
by instantiating a new class. So let's rerun this. We are disagreeing. That's great. So we've done what we wanted. This class is now read only and we cannot change any of its properties, right? Yes, yes, but not quite. There's a small catch. Remember we spoke about how objects are treated a little bit differently. They're passed by reference instead of value. That's the problem. That's the catch.
They're passed by reference instead of value. That's the problem. That's the catch. Yes, the properties cannot be overwritten. We cannot change the monthlyInterestRate. We also cannot change the invoice, and we cannot change the Carbon immutable, right? Yes, we cannot override them. But if we depend on an object, if we have an object as a dependency and that object is mutable, then we can mutate the object without overriding.
as a dependency and that object is mutable, then we can mutate the object without overriding that value on the existing object. What do we mean of this? Although invoiceInterestSimulation is not mutable, we cannot override any of its values invoiceS because invoice is an Eloquent model, and we know that Eloquent models are mutable. So although I cannot replace the invoice by a completely different invoice, it can mutate it.
So although I cannot replace the invoice by a completely different invoice, it can mutate it. The same will be true with Carbon or not a Carbon immutable instance. So here's what we're gonna do. We're gonna replace this with Carbon real quick, and then we're gonna go here and we're also gonna replace this with Carbon. There we go. And we're simply gonna do a dump and die on the simulation here and on the reference date. So let's run a test. So we can see the reference.
and die on the simulation here and on the reference date. So let's run a test. So we can see the reference date May 25th. And if I try to mutate the reference date, if I say I want the reference date to be today, and I read this, it is going to fail. We cannot modify a read-only property. However, if I mutate the object, hmm, let's see what happens. What if I say addMonth?
object, hmm, let's see what happens. What if I say addMonth? And you can see that we've added a month. We haven't replaced what the interest simulation is holding. It is still pointing to the same address, but with changed what the object it is related to is holding. So if we were to replace this with Carbon Immutable again, and obviously here as well, and we try to run this, then of course it wouldn't change.
and obviously here as well, and we try to run this, then of course it wouldn't change because we've already talked about this. Immutable DateTime did not mutate its value. They return a new object. The same thing that we are doing right here, instead of mutating its internal state, we are simply returning a new object. We're doing the exact same thing, the Carbon immutable or DateTime mutable due.
We're doing the exact same thing, the Carbon immutable or DateTime mutable due. And you're gonna notice this is pretty common with immutable structure. So that's really the only way to change an internal value. Seems you cannot mutate it. You have to return a new instance that has the new values you want to instead, a little bit more work, yes, but it's also more predictable. So let's get rid of this and let's run our tests.
a little bit more work, yes, but it's also more predictable. So let's get rid of this and let's run our tests and we are back to green. So quick recap. The readOnly keyword might make a property or the entire class immutable in the sense that its properties cannot be overrated. Now, if your class depends on objects, that does not mean those objects are immutable. If you have a mutable object, you'll still be able
that does not mean those objects are immutable. If you have a mutable object, you'll still be able to mutate it, and you're not going to be violating the immutability of the pairing class because it is immutable, it is still pointing to the same object. You've just changed the object itself, the related object. Therefore, if you need your entire dependency graph to be immutable, you've gotta make sure that all of your dependencies that are object are also immutable.
When to Use Readonly9:07
to be immutable, you've gotta make sure that all of your dependencies that are object are also immutable. Now, for the question, when should you use the readonly keyword? I could just say it depends and finish this video, but I'm going to give you my opinion. I tend to think of the readonly keyword of immutable object, uh, a zero cost benefit. I cannot see its advantage to using immutable objects, but I can see plenty of downsides for not using them.
I cannot see its advantage to using immutable objects, but I can see plenty of downsides for not using them. Therefore, if I'm creating a new class in the system, I'll typically go with readOnly. By default, it's essentially just adding a keyword. And if later down the road you want to change the class from immutable to mutable, you just remove the keyword and you're good to go. Now, if you have a mutable class and you want to make that immutable, then yeah, that's,
Now, if you have a mutable class and you want to make that immutable, then yeah, that's, that's quite a bit more work than going from immutable to mutable. Therefore, I typically make new classes immutable by default. There are a handful of situations where it might reach for immutability by default. Eloquent models are the prime example. You want them to be mutable.
Eloquent models are the prime example. You want them to be mutable. Sometimes you have classes that expose a fluent API and making them immutable, kind of makes their omics a little bit longy. But that's not always the case. So that it might also be a place where I'll make it mutable, although I'll probably start with immutable as well. And if I find that the API is not looking good and there's a lot of work to keep instantiating those new
And if I find that the API is not looking good and there's a lot of work to keep instantiating those new objects, maybe I'll make them mutable, but that's an exception. So to answer simply, I usually go for immutability. I always, I usually make classes read only, unless at some point I realize that, that that's not working, then I can just remove that keyword and move on with my day. Super easy. The exception that I want to mention is
and move on with my day. Super easy. The exception that I want to mention is existing classes within your system. As you've probably realized throughout this course, I did not recommend going through your existing classes and making changes to them because that is risky. Even if you have good test coverage, focus on the new classes that you're adding, and maybe when you're working with existing classes and you could see, maybe I could improve this somehow.
and maybe when you're working with existing classes and you could see, maybe I could improve this somehow. Maybe I could make this immutable. Maybe I could turn this into an object instead of an array. Yes, you can go with it, but I did not recommend going through your project trying to find something to fix, because sometimes there's nothing to be fixed. It could be improved, but it doesn't mean it is broken. So I would not suggest going around your system
It could be improved, but it doesn't mean it is broken. So I would not suggest going around your system and making classes read only unless you're very sure of what you're doing. You have really good task coverage and you can afford maybe breaking some things. Otherwise, just focus on new code and as you go back through existing code, you can improve it a little bit. With that said, I hope you all enjoyed this lesson.
you can improve it a little bit. With that said, I hope you all enjoyed this lesson and I'll see you on the next one. Bye.
