Taking liberties with value objects
Welcome to No Compromises, a peek into the mind of two old web devs who have seen some things. This is Joel.
And this is Aaron. Joel, you're more than just an object to me. I value you a lot.
Oh my goodness. We are off to a raucous start here. We're going to be talking about value objects, which is already nerdy and geeky and will probably make different people mad for different reasons. But like a pun to get us going that seems appropriate. I will set a little context here. I originally was trying to talk about value objects in a tip, and Aaron was like, "Nah, man." I think basically we try to make the tips in our newsletter short and it was turning out too long. And it's like maybe it's a series of tips and maybe I don't care. But then we're going to talk about it today. To first set the stage. If you've never heard that term value object, or maybe if you have heard it in either event, I think we should give a definition of what I'm talking about.
Yeah, tell me.
I will joke a little bit here and say, us Laravel developers have a somewhat notorious history of taking well-established terms and making them mean something else, right? Like facade. And I'm fine with that so I'm going to continue that tradition. So value object, if you're coming from the domain-driven design world, they have a very special meaning and a very technical meaning, which I'm not going to get into here. I'm just going to say that's not what I'm talking about. So when I say value object, I simply mean a class, an object.
A PHP object.
PHP object, which is an instance of a class. And the thing that makes it different or more specialized than just any old class is, generally speaking, you set its value when you create it and you don't change its value. Like, there's no mutable state. You might think of it as if I were to pass an array of eight values into some method, I might instead throw a value object because I can put types on it, I have to have all the data in a certain shape to create it. But it's not an object I'm creating and mutating and it's not living for a long time. Does that make sense? Did I help clarify this at all?
Yeah. A lot of times if it's an array that's coming in, that's already one step further, you already did some refactoring. But if it's a method that has like seven parameters or something like that, people start to call it a smell of, "Hey, there's too many properties coming in here, let's refactor that." So that's a good example saying we might create a value object for passing into a method or something like that, that needs a large amount of context and data and it needs it in a predictable processable shape.
Yeah. And with more recent, newer versions of PHP, this has gotten really nice to do. Like, in the past you kind of had to fake some things or you had to have some conventions in place, or there's a lot of boilerplate. But now literally you can create a class with just a constructor and you can do constructor promotion, right? And you can even set it read-only. So, you can basically have essentially a one-line constructor in a otherwise empty class and use it and you get some benefits from that. Maybe first I'll talk a little bit about some of the benefits and then we could talk about, like, when would you actually use it? We don't want to go to the other extreme, I don't think. And all of a sudden we're creating value objects for everything, we're passing a single value.
No, we're not programming in Java. We don't need String string = new String().
Ooh, yeah, I'm glad we're not there. So anyways, I think for me, the number one time I would reach for this is the scenario we already alluded to. Where you have a method where you just need to pass a lot of data into it. And what is a lot, I don't know. Is it three? Is it four? Is it ten?
Yeah. That's a internal or a team sort of limitation, but it's somewhere around more than a couple.
Yeah, I wouldn't do it for one or two. Like three, probably not either. But above that, I would start to think about it. And the first method of refractoring that most PHP devs go to is to make it an array. Because that's our bucket for everything. It's let's slap an array around it and we can pass that around. And now technically we have one parameter we're passing in, but that parameter has to contain seven keys. And, how do we specify what those keys are? We've sort of... We fixed one problem and I think we've created a new problem. Whereas, a value object you could specify those properties and you can specify if they have default values or if they're all required, and you can specify the types of them. And they all have to be provided at the beginning and you know you have a good value. So to me, that's the benefit. It's, I like types, I like things being clearly communicated in code, I like static analysis. And I think the value object helps us in this regard more than an array would.
Mm-hmm (affirmative). But why wouldn't I want to use one then?
Okay, why wouldn't you? I mean, some people do. Some people would create a value object or the other term you might hear is data transfer object, DTO. And they'll go nuts and they'll create them for everything. Again, I think here I wouldn't do that because I like to stick with Laravel conventions. Either that the framework establishes or things that are just sort of common patterns in the community. So if I'm not getting a benefit out of it, I don't think I want to have to create extra classes just for every method call I'm going to make between two things that require then one or two pieces of data. That's sort of my thing there. Is like, if it's not giving me extra value, I don't want the extra ceremony of having this extra class.
And I'm going to go just one step further nerdy and say a hard set rule would be, is the data I'm working with structured and does it require a specific order, or whatever? For example, if I had method that I had to pass in an array of bunch of colors, I'm not going to create a color or array object or a color object. If it doesn't matter the order and it doesn't matter whatever, I might just pass in that array there. So again, it has something to do with also that extra step, which is do I need this structure and is there any sort of rules around the data types and things like that that would make a reasonably large difference? Either a bug or change the structure of logic.
Sure.
Of course, you said stuff in a completely human way and I went the... But there is a nuance there that we just aren't necessarily mentioning, but it's a calculation that happens in your head.
Yeah. Well, one other thing I was thinking of as you said it... Well, two things. One is with PHP DocBlocks, you can go nuts and specify array shapes. I just don't like that syntax. I think it's weird, especially when we have something native in PHP to do it. So that's just maybe one acknowledgement if somebody was thinking, "Hey, just use array shape in your PHP DocBlocks." You could do that and you would get some of the static analysis benefits I'm talking about. But the second thing, like, another reason a value object is useful. I talked about we're not going to mutate the state and it can be a simple constructor in an otherwise empty class, but it doesn't have to be. So maybe the other time this would be valuable is if we need that data in a structured way that's different than the way we provide it. I'm trying to think of an example that won't require a ton of setup. But let's just say you had an object that represented configuration data for a system, and maybe at the top level there's two or three keys that you have to provide and each of those keys can have a variety of values set. Some of that you could maybe do with an enum, like, "Here are the three possible keys," or even configure-
I think you can do default values if it's coming in saying, "You have these two or three settings, and you provide zero to three configuration settings." Then, the completed setting object could be a mix of the two things. But we're kind of then going a little bit from value objects into just classes.
Right. But I think... And again, I am altering the definition to suit my personal preferences. But yes, in the traditional sense, you might not have helper methods on a traditional domain value object. But for what we're doing here, the thing that would make me call it a value object still is sort of the idea that the data is sent once when it's constructed and the data is never changed. I'm only adding helpers to it. So you can put just like you can put helper methods on an enum. Obviously, you can't change the internal state of the values of an enum at runtime. You probably shouldn't. I guess you could do anything you want with reflection, but it's sort of the same thing. Like, if I want a cast, I want to take this value object and be able to serialize it so I can stick it in an Eloquent model, which is going to get written to the database. That could be a useful thing too. Where else would that logic live? Like, it really makes sense to kind of put it in the thing that's holding the data too, even if we're not mutating that data. So if you haven't completely gone to sleep at this point, I think it's interesting to consider. And maybe the call to action here is, think about your code. Are there places where you have just a ton of parameters being sent in or where it's just kind of painful? Like, "Ah, we have to add one thing," or, "This particular call site only needs some of these options, this one needs another." It's just something to consider that could be a useful way to introduce a value object.
I don't know if it's this way everywhere in the world, but when we celebrate Halloween in the United States, everything that kids know about the world goes out the window for one day. I just don't understand it. We say, "Don't talk to strangers and certainly don't let them take pictures of you." And then on this one day we're like, "Please talk to strangers and they're going to take pictures of you." And say, "Don't walk up to random houses by yourself." A lot of times the parents will step back and let the kid go up by themselves. And then it's like, "Don't take candy from strangers," except for this one day when the job is to take all of the candy.
I think the lesson here is that it is training kids to be more independent and to not listen to these rules. Like, "See, it was fine this one day. Nothing bad happened."
I always follow the rules unless there's candy. If you're like me and you don't really necessarily listen to the first few seconds of a podcast or anything really, you might have missed what Joel said when he first started.
I was talking about our newsletter and the daily Laravel tips. If you want to check that out, head over to masteringlaravel.io and the signup form and all the previous tips are available on that site.