Leveling Up: Evaluating Value Objects
Humans are pretty good at looking at things and telling what they are, in general. If I were to ask you what “red” was, you’d likely tell me it’s a color and perhaps list a few things that are red. If I were to ask what 8675309 was, you might say it’s a phone number, or perhaps it’s the number of animals adopted each year, or if you’re more of a math geek you could tell me it is the 582161st prime number. (It’s true, look it up). However, context can change what each of these mean and context is very important.
If you’re listening to Tommy Tutone, the 867-5309 is definitely Jenny’s number. And red might be a color, but what if I was talking about “That 70’s Show”? Now it’s just as likely that it refers to “Red”, Eric’s father. Or perhaps it’s a reference to DEFCON levels and we’ve got some serious things to think about. As humans, we are able to gather context from the things that happen around us. If we’re standing in front of the WOPR computer, red is more likely to refer DEFCON levels, for instance. Computers don’t have that luxury. They rely on the context in-so-far as we give it to them. Sometimes this context is just the variable type. And because PHP is built for the web and the web is built on strings, it happily converts from one type of variable to another. Often we get the right answer, but other times we get surprising and even onerous results.
In PHP, we can add the two strings “2” and “3” and end up with the integer 5. PHP automatically saw that we were trying to add these two strings together and converted them to numbers and added them. Sometimes, though, we don’t get what we may expect. Those of us who have been working with PHP for awhile will not be surprised to know that 204 + "1600 Pennsylvania Ave" is 1804. In fact, if you run this in PHP, you’ll get an integer of 1804. Depending on your version, though, you may get just that result, or if you’re running PHP 7.1, you’ll get a notice that “1600 Pennsylvania Ave” is not a well formed numeric value.
If we look at what the values are, or represent, and try to do the same operation, things start to look wonky. First of all, we’ve got 204 which is a number, specifically an integer. What it represents, we have no idea. We also have “1600 Pennslyvania Ave” which presumably represents an address. If we were to add int and int, chances are we’d expect an integer as the result. This seems reasonable and logical. However, what is expected if we added int and Address? I have no idea what we’d expect the system to do. We could say that it should add the int value to the street address part of the address. Or just as reasonable, expect that the int value represents the number of blocks north to move with the expected value being the new address. Or any other of a number of potential “expected” results, none of which makes any more sense than another. As a computer or as a programmer, when faced with the task of trying to add an integer to an Address, we could immediately indicate that there was a problem because we’re being asked to do something that makes no sense.
Not All === Values Are Created Equal
PHP has a number of built-in types. There’s the commonly known int, float, string, boolean and array. There’s also null, resource, object and a new callable type. However, those don’t tell us the context or meaning behind a value. When you run through your code, there’s a decent chance you’ll run into various integers or strings right in the code. You may have a loop that counts up to 4 or a section of code the uses “GET” to retrieve something. There may be another 4 somewhere else or another “GET” elsewhere as well. These sneaky bits of data are known as “magic constants”. They are hard coded values that are needed in order to perform some kind of functionality. But their meaning is what is missing. Once of the “4"s might be looping over a quadrilateral and determining its perimeter. The other may be used to build a table with four columns. Clearly replacing these with the same variable, while it may work at first, is not the right path.
Some magic constants can be replaced with actual constants. On a project I work on there’s database table which contains the status of some virtual crops the user works to cultivate. If it has a status of “0” then it’s considered active, alive and growing. If it has a status of “1” it has been sold and the record is kept for historical tracking reasons. If the status is “2” then it has exploded. Rather than have a codebase littered with 0, 1 and 2, it helps provide context by using constants like STATUS_ACTIVE, STATUS_SOLD or STATUS_EXPLODED. It helps the developer understand what the intent was rather than needing to remember what the values are and what the previous developer was trying to do.
But we can go even further in most cases. That’s where Value Objects come in, that is what we’ll be discussing for the remainder of this article.
Unlimited Types
PHP may only come with 8 built-in types, but we have the ability to create as many types as we want. To me, it makes sense to create these types even if we would normally represent the value by a single primitive type. In that case, we will be wrapping a primitive value in an object. The object carries with it the context – the reason for the primitive value to exist. For instance, suppose we need to deal with phone numbers. It’s tempting, fairly easy, and extremely common that we represent a phone number by a string. This gets passed around, stored in databases, represented on the screen and mangled in various ways. But in order to avoid bugs, we need to add ever-increasing checks to ensure that everything is right and happy in phone number land.
In the U.S., nearly everywhere uses 10-digit dialing. We have a 3-digit area code, followed by a 3-digit exchange and a 4-digit number. These ten digits, in order, make up what many of us see as a phone number. In some cases, the phone number may need to be formatted. It could be done in a number of ways: (720)555-1234 or 720-555-1234 or 720.555.1234 or even +1 720 555 1234, or any number of other formats. All of them represent a phone number, and if we’re expecting users to provide a phone number, we should allow them to enter any of these, and more, and deal with it appropriately. Notice, on the last example, the user provided the country code which means we’ve got 11 digits provided. Depending on your needs, you may also need to allow a user to input an “alpha” phone number, that is one which is commonly used in advertising for memorability - “318-4-PHP-411” for instance.
If the methods and functions we build that need a phone number accept and deal with strings, they either need to be able to deal with all the variations along with all the possible invalid variations and validations. This could be all over the place. Even if you build functions for doing all this, there would still likely be repeated blocks of logic to determine if the number is international, if it’s correct, etc.
Instead, we could create a PhoneNumber class. Whenever we need a phone number, we can pass the wrapped string around in a PhoneNumber object and we would know that we are expecting a phone number. The PhoneNumber class, in turn, would be able to ensure that we have a valid phone number, and could also give back various primitive representations of the number - formatted, no punctuation, internationalized with country code, original formatting, etc. This would allow code that requires a PhoneNumber to type hint on that class, or better yet, type hint on an interface that the PhoneNumber class implements. (This would allow for different implementations of classes that can give back phone number information, even if that class is not solely representing a phone number.) Then in that code, the developer understand that she’s not dealing with a string that looks like a phone number, but an actual phone number. The fact that internally it’s a string doesn’t matter. In fact, internally, it could be represented by multiple fields - country code, area code, exchange, number, etc. The class doesn’t need to expose how it’s dealing with the data internally.
Wrapping Primitives and More
There are plenty of values that we use in code that can be represented by a single primitive value. There are also plenty where there are multiple values. Too often in PHP, we rely on the ever-present array to give us flexibility. Plenty of code ends up expanding to multiple parameters and loads of defaulted values until someone decides that it’s too many. Then they change the signature to accept an array of key => value parameters. The code may have even taken that into consideration and left some required parameters as standard parameters and then put all the optional values into a $params array. Problem solved, right? Not so much. Now we don’t know if we have a parameter or not. There’s no such thing as a required key in an array.
Now inside the code, we either need to decide we’re fine with PHP throwing notices for accessing keys that don’t exist, or we need to wrap everything in isset before using it, or with PHP 7+, we can use the awesome Null Coalescing Operator. Go back and read that again, but imagine your voice is resonating and echoing and sounds amazingly cool. Ok, here it is again - The Null Coalescing Operator! Fun, right? The null coalescing operator is a great way to quickly determine if a value is set in an array (or another variable) and provide defaults, all without PHP complaining that you’re accessing a thing that doesn’t exist. So back from that aside, we either need to decide we’re ok with PHP emitting notices or wrap with isset or use ??. And we should NOT be ok with notices. Even if they are disabled, PHP still does all the work to figure out you’re accessing stuff that doesn’t exist, plus making the message and then it throws it all way. Code that emits notices (or warnings) is going to be slower than cleanly coded PHP, even if you don’t see the notices.
We also should not be OK with calling isset to determine if something was set in an array that is used as a params bucket or a poor substitute for an object. We shouldn’t even be happy with using the Null Coalescing Operator (echo echo) as a substitute for knowing what we’re dealing with. Instead by creating value objects, we can be certain that they have specific things we need and that they are valid.
Examples
Suppose we need to deal with email addresses. Instead of using a string to represent an email address, we can build a value object.
class EmailAddress
{
private $address;
public function __construct($email)
{
$this->address = $email;
}
public function getEmailAddress()
{
return $this->address;
}
}
It’s simple and straightforward, but it gives us a start. Now when we need to work with an email address, we don’t have to rely on a developer seeing that we named our parameter $email, we type hint EmailAddress.
public function passwordReset(EmailAddress $email, string $content)
{
// Send password reset email
}
Now there’s no question what’s expected. We don’t even need to be concerned when calling it, what is going to happen with it. If the EmailAddress value object provided a way to extract and return the domain, we’re still covered. In fact, we could take the example above another step further. Instead of relying on a string for $content, we could make an EmailContent class. This could potentially encapsulate both a text and an HTML version of the email we want to send.
As a caller, I don’t need to be concerned that the password reset function will only send a text-based password reset email. I just know I need to provide an EmailContent object with the content of the email. Later, if we decide that we want to send a mixed message with HTML and text, I would only need to update the passwordReset method to extract both HMTL and text or even more likely, the passwordReset method would be passing this off to something else that’s responsible for actually assembling the email.
Value Objects Should Be Immutable
We talked last month about immutability. That is, the objects internal state should not be allowed to change. This is because the way we should be able to compare value objects isn’t by seeing if they are the same object, but rather if the state is the same. Imagine two email address value objects:
$email1 = new EmailAddress('phparchitect@davidstockton.com');
$email2 = new EmailAddress('phparchitect@davidstockton.com');
var_dump($email1 == $email2); // true
var_dump($email1 === $email2); // false
In short, it’s the values in a value object that make the value objects comparable, not the actual object itself. I’ve given the example of using DateTime vs DateTimeImmutable in a number of previous columns so I’ll spare you in this one. If you recall, though, we want to compare date objects based on the date and time they contain, not on whether they are literally the same object. Using the immutable version of DateTime means we can avoid a lot of different and hard-to-track errors cause by inadvertently changing the state of an object when we really wanted to create a new object based on an existing one.
How Are Value Objects Different From Entities
You may be wondering how a value object is different from an entity. The identity of an entity object is because of some sort of unique identifier. All the other state of those objects can change, but it would still be considered the same entity. For a value object, if the state changes, we now have something else entirely. Conversely, two value objects that have the same state ought to be considered equal. Consider the phone number and email address examples. If the value of the string representing either one were to change, we’ve now got what we’d consider to be a new value object. The phone number 318-474-7411 is certainly not the same as 720-555-1234 even if we happened to use a mutable object and change the state. However, if you consider an entity object representing a person, even if we change the person’s name or phone number or email address, they are still the same person. Their identity is not held within the state of the entity object.
This is another point towards the importance of immutability of the value object. Our entities have a history. There was a path that was taken in order to get them to the current state. We may not store that history in the object or in the database, but there was, somehow, a history nevertheless. For a person, their history may include the assignment of a name shortly after birth. Later, other state changes happen as they grow and age and make other life choices. All of these changes are part of the history of the object representing that person. However, something like a date, a value object, has no history. It simply “is”. Changing its state means we’re dealing with something fundamentally different than what we started with.
Consider how money could be treated. If we modeled physical dollars as objects, including state, we’d potentially include information about the serial number. But we want to represent money as something fungible - where any dollar is equivalent to any other. If we changed the state, we’re dealing with something else. If the state of a dollar included its value, and we were allowed to change it, we’re now dealing with counterfeiting charges as well as something else entirely.
Considerations and Conclusion
Wrapping primitives in an object seems at first like a lot of unnecessary overhead. Indeed, there is some overhead, but not that much. For the help the developer receives from their IDE and from the self-documenting of the code we get from type-hinting on an object, to the potential errors that can be caught both automatically and through code inspection, I surmise that this trade-off is well worth it. The value objects are cheap to create and destroy. It is simple to know what you’re doing is correct according to the code you’re calling. If you provide a well-formed value object to a method that type-hints that it needs that kind of object, you should be well assured that it’s going to work appropriately. Compared to passing in arrays of data, it is superior. The code you’re calling needn’t be concerned about whether an optional item was provided, or what to do if it was or was not provided. It simply uses the object. The object can encapsulate logic about what it means if optional values are provided. Additionally, the object can include methods that help with extracting relevant bits of information from the state that would otherwise require global functions or loads of copy-pasted code to deal with throughout a site.
Consider wrapping your primitive types in an object that represents the intent and the context of what that value means. It will help increase understanding of the code and simultaneously reducing errors. Value objects provide an excellent way of shuffling data around along with their context, giving simple types a more coherent and stronger meaning, which allows the programmer to concentrate on getting the logic of the program correct instead of fighting through low-level data manipulations and scattering of business logic and code intent all over the codebase. It may seem weird a but I think it will be worth it. See you next time.