When to Use Value Objects0:00
The next option we have when refactoring large God objects is to extract a value object, but only, I would recommend, when it makes sense. And I say that because, at least from my experiences, I've found that, especially php developers, they learn about value objects, and they read a DDD book, and then suddenly every possible primitive has to be a value object. And it's bad design unless you do that. And, personally, I find that a little silly. They are useful when it makes sense. They are not useful as a default. You're just making more work for yourself at that point. So imagine this. I have a Performance model here, and, specifically, I have a column related to revenue. So I will just return that. Now, personally, I store money or revenue as cents in my database. For example, a Laracasts yearly subscription is $86, but in the database it would be stored as $0.86.
Adding Revenue Helpers0:46
Now, personally, I store money or revenue as cents in my database. For example, a Laracast yearly subscription is $86, but in the database it would be stored as $0.86. So maybe you decide over time, well, it would be nice if I could convert that to dollars, so cents to dollars. Okay, so you might do something like this. You add a new method here, and you say revenueInDollars, and all that's going to do is call your revenue method and divide it by 100. All right, so now you have 86, and everything is great. But then a couple months go by, and you find that in multiple locations you keep having to format this value as currency. Now, there's a good argument to be made for creating a dedicated Presenter class for that, and we can review that later in this series. But for now, let's just imagine you're keeping it simple, and you're putting that on the model as well. All right, so revenueAsCurrency.
and we can review that later in this series. But for now, let's just imagine you're keeping it simple, and you're putting that on the model as well. All right, so revenue as currency. And now this one is going to return a call to money_format, and we'll say we want a dollar sign, and then the value, and then we will reference revenue in dollars. Okay, so now this should give you a formatted version of that amount. All right, so right now this is fine. It's only three methods, right? And in fact, it's the only three methods. But once again, use your imagination. Imagine two years worth of work on this class, and it keeps growing and growing and growing. Well, one option you have if you want to clean it up is to extract a value object here. And notice we have a little hint. We have revenue, and then revenue again, and then revenue again. So the fact that I keep using that prefix is sort of like a hint that I might be missing some kind of abstraction. So
Extracting the Revenue Object2:18
hint. We have revenue, and then revenue again, and then revenue again. So the fact that I keep using that prefix is sort of like a hint that I might be missing some kind of abstraction. So here's what we can do. I'm going to create a new class called Revenue, and this will be our value object. And now a quick note on this. Once again, on naming, so many people will do things like this, RevenueVO, or I've even seen that. I'm not a fan. Just stick with Revenue. That's what it is. You don't have to define the underlying pattern or anything like that. Okay, so let's set this up. Still in the App namespace, and we have our Revenue class. All right, so for a value object, there's honestly a lot of rules. If you read a DDD book, it can almost be overwhelming. I like to keep it very simple. If I find that for a particular primitive, something like this, a number, if I find that there's a lot of behavior associated with it, in this case, converting it
I like to keep it very simple. If I find that for a particular primitive, something like this, a number, if I find that there's a lot of behavior associated with it, in this case, converting it to dollars or formatting it as currency, well, there's extra behavior there. So we could instead move that to its own class, which we call a ValueObject. So for example, I could take these two guys, comment them out, switch over to our new ValueObject, and paste them in. Next, notice how the revenue prefix is sort of redundant now. That's kind of a hint that we're on the right track. So I will change that to N dollars and then as currency. Next though, how do we get access to the actual value, the number, in this case, 8600. Okay, let's pass that through. revenue, and we will assign it. Now up here, we could make this public. A lot of people would squawk at it. I think at the end of the day, it doesn't matter that much, depending upon the project you're.
Returning Value Object via Accessor4:38
one. You would create a new one. Next, there's also a lot of rules about how you would compare one value object with another and how a value object represents the value itself and not the underlying ID in a database or its identity. Lots of stuff there. Again though, I like to keep it very simple. If I find behavior associated with a string or primitive, and I'm finding more and more of it, only then will I consider extracting a dedicated class to store that behavior. So now, if we switch back, well, we still have this section here. So how do we, if we have an Eloquent model, how do we turn performance revenue into a value object? Because right now, it's still going to refer to the actual value. Well, here's what we can do. I can instead change this to getRevenueAttribute. This is a custom accessor that Eloquent knows about. So basically, what happens is when you access the revenue field, Eloquent will see, oh, you have this method defined. So I'm going to
This is a custom accessor that Eloquent knows about. So basically, what happens is when you access the revenue field, Eloquent will see, oh, you have this method defined. So I'm going to pipe the value through there, and then you can decide how you want to format that or what you want to do in response. That means in our case, I could say return new Revenue and then pass that through the constructor. This means now, on your Performance model, when you access that revenue property, you're actually getting an instance of this class. So now, down here, I can just reference that as a property, and this looks good. Let me show you. php artisan tinker. I don't have anything in the database, so we will new one up. P for Performance, we will new that up. We'll set the revenue equal to 8,600, and now take a look. If I access that property, I no longer get 8,600. I get an instance of Revenue, which now has all this additional behavior like this. Revenue in dollars.
revenue equal to 8600, and now take a look. If I access that property, I no longer get 8600. I get an instance of Revenue, which now has all this additional behavior like this. revenueInDollars and revenueAsCurrency. And whoops, we have a mistake. You know what? I forgot to change that one. I know you saw that. One more time, just to prove it to you. And now, this time, if we run it, we get the proper value. Okay, so kind of cool, right? An interesting thing to think about. If we come back, before we had three, maybe four methods going on this class, but now I can delete all of that and replace it with a single accessor that returns our value object. But now we have one final thing. What about when I'm in my view and I still want to echo out the revenue? Well, right now, if I try to access the revenue, yeah, notice this. Object of class Revenue could not be converted to a string. So I don't want to lose that functionality. That's really useful. So here's
Defining String Representation7:17
now, if I try to access the revenue, yeah, notice this. Object of class revenue could not be converted to a string. So I don't want to lose that functionality. That's really useful. So here's what we do. We come back to our VO and we define what should happen when we reference this object as a string. And to do that, we can use this magic method __toString(). So once again, if I try to reference the object as a string, what should happen? We're just going to return the revenue itself. But now, in this case, revenue is an integer, not a string like this. So we need to make sure that we cast it to a string properly. Okay, but now, otherwise, you might also decide that the string representation for this is the asCurrency version here. Let me show you both. Once again, boot up php artisan tinker. Let's call this. And now, yeah, echo $revenue. And we do call that method, so we get the string. But now, if we decide we want this asCurrency to be the default, that's fine too.
Adding Comparison Behavior8:08
Tynker. Let's call this. And now, yeah, echo $revenue. And we do call that method, so we get the string. But now, if we decide we want this asCurrency to be the default, that's fine too. Come back one more time. And now, if we echo it, we actually get the formatted version, like so. And think about it. Now, in your reports, if you ever want to say, well, if the $revenue is greater than yesterday's $revenue, then blah, blah, blah. Well, now, if you want, you could add a dedicated method here. You could say isGreaterThan. That way, you don't have to do a long conditional. You could have something like this. Or like outperformed the previous day. And that would be a Revenue object as well. Yeah, stuff like that. This is kind of a cool place to put that logic. It makes your code that much more readable. All right. So you've created your first dedicated value object. And now, notice our Performance model before was three or four methods. And now,
It makes your code that much more readable. All right. So you've created your first dedicated ValueObject. And now, notice our Performance model before was three or four methods. And now, it's just a single one that defers to our ValueObject.
