Steve Grunwell

Open-source contributor, speaker, and electronics tinkerer

Taco, a black cat, peeking out of a white drawer

The Beauty of PHP Value Objects

At last year’s php[tek], one of my biggest “holy cow, why haven’t I been doing this?!” moments came from my friend Andrew Cassell when he explained PHP Value Objects in the context of Domain-Driven Design.

Put simply, a Value Object is an immutable object that encapsulates some data and will always be in a valid state.

For a bare-bones example, let’s encatsulate my cat, Taco:

Now I can pass my Cat instance around and I know that it will always be valid.

I could type-hint against it:

…or even filter a collection of animals to find the cats:

Anywhere I have a Cat object, I also know that it will have a getName() method that will return a string.

Value Objects should always be in a valid state

What we can’t be sure of (yet, anyway) is that the name will be empty. After all, new Cat('') is technically valid, right?

Let’s add some validation into our Cat definition:

Now, if we try to construct a Cat with an empty name, we’ll get an InvalidArgumentException:

If we add other properties to the object, we can validate those, too. The end goal is that once we’ve constructed the Value Object, we know that it’s in a valid state!

This can also be a great place to leverage custom Exception classes: InvalidArgumentException is a great default, but it’s even easier to understand what’s going on if you’ve defined an InvalidAnimalNameException class.

Understanding immutability

Another key feature of Value Objects is that they are immutable, meaning that once they’re created they do not change. These are not the same as (for instance) a database model; there’s no primary key nor save() method, because that’s not the purpose of a Value Object.

Notice how there’s no setName() method in our Cat class? If you want a cat with a different name, create one!

A more practical example

I like to think of Value Objects as custom types for handling certain types of data. One common use in web applications would be email addresses: while these are strings, not all strings are valid emails. If you try to send a newsletter to the email address “taco”, you’re going to have a very confused mail server.

Additionally, email addresses are one of those things that we tend to validate over and over again, which means our codebases become littered with calls to filter_var($email, FILTER_VALIDATE_EMAIL).

Value Objects can help us here, because we can put that validation in one place and be sure that it’s valid:

Now, any time we’re given an email address as a string, we can throw that into our Email value object; if the address is invalid, we’ll get an exception telling us so. We also know that getEmail() is guaranteed to give us a valid email address:

We don’t need to worry about invoking any other sort of validation tool, because we’ve already defined how we want to validate email addresses in the Email class!

Adding other methods

A Value Object isn’t limited to just the values you pass into the constructor. Let’s say we wanted an easy way to get the domain from an email address; we might write a getDomain() method that looks something like this:

Note: I’ve chosen to split email addresses on the “@” symbol, but this is for the sake of simplicity in this example; RFC 822 and its kin are a whole separate can of worms!

Now, anywhere we need to get the domain associated with an email address, it’s as simple as calling getDomain()on the Value Object!

Unit testing Value Objects

Another great aspect of Value Objects is that they’re very straight-forward to test:

That’s it! Now anywhere you use the Email class you have a fully-tested, immutable, and always-valid email address!

Previous

Why I’m Dropping Google Analytics

Next

Decouple Your Application Code with the Adapter Pattern

2 Comments

  1. Joe T

    Awww. Such a cute kitty.

    i know for the sake of simplicity it’s easy to write “array_pop(some_expression())” but it always makes me flinch.

    Aside from that, thanks for the post. :)

    • Oof, you’re 100% right. Thank you for the code review, I’ve updated it to avoid passing anything but a variable by reference.

      You win 10 Taco Points!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Be excellent to each other.