A better way of writing value objects in PHP

Chris Harrison
Jan 16, 2018 · 3 min read

At Funeral Zone, we’re into Domain Driven Design. We’ve been writing value objects in PHP for two years but have recently refined our approach. This article explains our philosophy behind implementing value objects in PHP 7.

We’ve open-sourced our value objects library on Github.

Quick introduction to value objects

A value object is a concept from Domain Driven Design. They are objects whose equality is not dependent on identity.

$location1 = new Location(1.2902, 103.8519);
$location2 = new Location(1.2902, 103.8519);

$location1 and $location2 are considered equal because they both have the same latitude and longitude. The definition of a Location is purely based on its value. If the values change, it's a new location.

$id1 = 1021;
$id2 = 1031;
$userId = 9302;
$products = [2901, 9303, 0185];
$order1 = new Order($id1, $userId, $productIds);
$order2 = new Order($id2, $userId, $productIds);

These two orders are not identical. Even though they share the same values, they are two different orders. Their identity is not based purely on their value. An ID has been added to the object as a way of encapsulating its identity. An Order is an entity not a value object.

Our approach

You should not be relying on a library to define your value objects. Value objects are just values in your domain. They should be completely specific to your business logic. In an ideal world, they should just be pure PHP objects, written from scratch for each value in your domain.

Practically, that would lead to a lot of boilerplate and copy-pasting to achieve common tasks like comparison and serialisation. So developers go about creating a “value object library” to save time. Typically, the approach is to provide a bunch of generic, reusable value objects. This approach quickly descends into code like this:

public function changeUserEmail(GenericIdObject $id, GenericEmailObject $email);

Where did ‘GenericIdObject’ come from? If that’s part of the ubiquitous language of your domain, something’s gone seriously wrong with your domain modelling process.

What’s happened is arbitrary application language (from your third party value objects library) has crept into your domain. Much better would be to conform the naming of your value objects to actual domain language like so:

public function changeUserEmail(UserId $id, Email $email);

You can achieve this with typical value object libraries by using inheritance.

class UserId extends GenericIdObject {
...

But your value object is still being influenced by the underlying generic class you’re inheriting from. UserId is still of the GenericIdObject type. It shouldn't be. UserId should be a pure type.

Our value object library attempts to solve this issue by taking a different approach. Instead of supplying you with lots of generic value objects you can extend from, we give you the tools to create your own pure value objects from scratch. This is mainly achieved through the use of:

  • a small interface (it’s as tiny as we think it can be whilst still being useful)
  • a set of PHP traits you can use to help implement the interface

This is the interface:

interface ValueObject
{
public function isNull(): bool;
public function isSame(ValueObject $object): bool;
public static function fromNative($native);
public function toNative();
}
  • isNull A simple boolean value of whether the value is null or not.
  • isSame Whether the value of this ValueObject can be considered equivalent to another ValueObject
  • fromNative and toNative are essentially for serialisation. For getting your value objects in and out of your application (e.g. persisting and retrieving from a database)

All you have to do to create a new value object is write a class which implements the ValueObject interface. You don't extend from anywhere else. You don't have to write large amounts of code to conform to a bloated interface.

Of course, if your domain is dealing with the same types of value over and over again, you don’t want to have to keep coding the same logic hundreds of times. So we’ve provided some traits which implement some or all of the interface. They can be found under the ‘Scalars’ directory in our package.

Unfortunately, we’ve had to diverge from traits to provide more complex functionality. In order to implement more complex features like sets and nullables (the subject of a future post!) we’ve had to introduce a couple of abstract classes.

But at no point while using this library will you be able to inherit from some of our code without having to at least write a couple of lines of code to customise the object to your use case.

For details on actually implementing value objects see the README on Github.

We hope this will lead to better quality value objects, that are more domain specific, with minimal effort.

Funeral Zone Engineering

Musings, learnings and sharing from the design and…

Thanks to Adam Ainsworth, Decebal Dobrica, and Kevin Baldwyn

Chris Harrison

Written by

Software engineer

Funeral Zone Engineering

Musings, learnings and sharing from the design and engineering team at Funeral Zone

Chris Harrison

Written by

Software engineer

Funeral Zone Engineering

Musings, learnings and sharing from the design and engineering team at Funeral Zone

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store