Update (4th June 2014): Value Objects can now be used in Doctrine 2.5 (currently still in Beta), using the new 'Embeddables' feature: http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html. An example is given at the end of this post.
An aspect of Domain Driven Design (DDD) that I find quite appealing is the differentiation between entities and value objects. I don't think you even need to embrace DDD as a whole to benefit from this distinction (I'm not saying you shouldn't embrace DDD, just that this aspect of it stands on its own). If you are using Doctrine 2 though, there is no native support for value objects (not yet anyway), which leaves you with the problem of mapping them yourself. In this post I will talk a bit about the difference between entities and value objects and give one suggestion of how to handle value objects when using Doctrine 2.
An aspect of Domain Driven Design (DDD) that I find quite appealing is the differentiation between entities and value objects. I don't think you even need to embrace DDD as a whole to benefit from this distinction (I'm not saying you shouldn't embrace DDD, just that this aspect of it stands on its own). If you are using Doctrine 2 though, there is no native support for value objects (not yet anyway), which leaves you with the problem of mapping them yourself. In this post I will talk a bit about the difference between entities and value objects and give one suggestion of how to handle value objects when using Doctrine 2.
What is an entity?
An entity is a class that represents
something which has an identity independent of its properties. In
other words, even if some of its properties change, its identity
remains the same. Entities normally represent the fundamental
building blocks of your application.
For example, a Customer would be an
entity. A customer record might have various properties like name,
address, email address, order list, etc., but even if you change one
of those values (eg. if the customer moves house), it is still the
same customer.
What is a value object?
A value object is a class (usually
small, with just a few properties, and frequently used), that
represents a value which has no identity separate from its
properties. If any one of its properties change, it is no longer the
same value.
An example of a value object is an
address. If you change any part of an address, it becomes a different
address. Another example would be a price - it might have properties
for currency, amount, and payment frequency, but changing any of
those would yield a new price.
As such, value objects are immutable -
their properties cannot change (it is normal to use getters but not setters for the properties of value objects). If two value objects have
exactly the same properties, they can be regarded as equal.
Value objects can have methods, but the
behaviour of a method doesn't ever change the object's state. If a
method needs to change a property, it will return a brand new
instance of the value object instead of modifying the existing one.
Methods on a value object can also return other types of value of
course (eg. a boolean return value would be typical for a validate
method).
Can a Value Object have an Entity as a property?
It doesn't happen very often, but there
is nothing wrong with referencing an entity from a value object -
having a reference to an entity does not force a value to become an
entity. For example, an address value object would typically include
a country property. Whilst this would normally be a string, your
application might need to have a Country class which has mutable
properties (such as whether the country is a member of the EU) and is
therefore an entity. But you could still have a country property on
your address value object, even if it refers to an entity (I will use this example in the sample code below).
Can an address/price/date range/etc. ever be an Entity?
Yes! Whether something is an entity or
a value object depends on how you intend to use it. If you were
writing an application for a postal service or a town planner, an
address might be more than just a value - you might have the power to
change properties of an address without changing its identity (eg. if
you can assign house numbers or postcodes).
Why use value objects?
This is really two separate questions,
one of which is a little easier to answer than the other...
Why use value objects instead of scalar values?
This is the easy one. You could have a
Customer class with properties for address_line_1, address_line_2,
etc. - each as a string. Or a phone number could be held as a string
with the area code in brackets. By collecting these properties
together into a single value object, or separating a string into
constituent parts as separate properties of a value object, it is not
hard to see that you will gain many OOP benefits. If there are
several pieces of data that go together to make a single domain
concept, they belong in an object. You can then perform computations
on that discrete set of data, validate it, re-use it, and extend it.
It also allows you to separate concerns - your entity does not have
to worry about any domain logic relating to the value, leading to
cleaner code.
Why use value objects instead of Entities?
Why not just give everything an ID and
make it an entity? There's no technical reason why you couldn't have
an address ID for example, and even allow the properties to be
modified. However, although at first glance it might seem as though
separating out entities and value objects makes things more
complicated, it actually makes things simpler. Why give an address an
ID if it doesn't need one? It unnecessarily complicates your model,
and could also cause confusion as it is not clear how you intend the
data to be used. If something is defined as a value object, we know
what it is, that it will not change, and that another object of the
same type with the same properties is considered equal to it. This
gives us lots more information on how it is meant to be used - so the
conceptual difference is valuable in making your code understandable
and easy to maintain.
Having said that, if you are using an
ORM which does not support value objects (such as Doctrine 2), there
is indeed some additional complexity involved, as you need to provide
some way of mapping custom value objects to database columns
yourself. If you can do this in a way that will be easy to refactor
later (when hopefully the next version of Doctrine will support value
objects), it might still be worthwhile doing that extra legwork while
defining your entities so that you can reap the rewards of improved
code clarity and usability.
Using Value Objects with Doctrine 2
Let's take a simplified example. Here
is a basic Customer class, including phpDoc annotations for Doctrine
2 (the BaseEntity class it derives from [not shown] contains magic
methods __get and __set for accessing the protected properties, as
Doctrine 2 doesn't like you to use public properties):
namespace Entities; /** * @Entity @Table(name="customer") * @property int $id * @property string $name * @property \ValueObjects\Address $address * @property string $email_address * @property string $telephone **/ class Customer extends BaseEntity { /** @var integer @Id @GeneratedValue @Column(type="integer") */ protected $id; /** @var string @Column(type="string") */ protected $name = ''; /** @var \ValueObjects\Address */ protected $address; /** @var string @Column(type="string", length=150) **/ protected $email_address = ''; /** @var string @Column(type="string", length=40) **/ protected $telephone = ''; }
Here we have an Address value object as one of our properties, but we can't map it directly to a column, because it is made up of several fields and Doctrine 2 currently won't resolve them for us. We could give Address an ID and make it an entity, and Doctrine 2 would then be able to map it, but if we want to use a value object, we have to do our own mapping, which I will come to in a minute. First, here is the Address class (the BaseValueObject it derives from has a magic getter but no setter, so the properties are read only):
namespace ValueObjects; class Address extends BaseValueObject { /** @var string **/ protected $line_1 = ""; /** @var string **/ protected $line_2 = ""; /** @var string **/ protected $line_3 = ""; /** @var string **/ protected $town = ""; /** @var string **/ protected $state = ""; /** @var string **/ protected $postcode = ""; /** @var \Entities\Country **/ protected $country; public function __construct($line_1 = '', $line_2 = '', $line_3 = '', $town = '', $state = '', $postcode = '', \Entities\Country $country = null) { $this->line_1 = $line_1; $this->line_2 = $line_2; $this->line_3 = $line_3; $this->town = $town; $this->state = $state; $this->postcode = $postcode; $this->country = $country; } }
As described above, the country property of the Address value object here refers to an entity (although it could very well refer to another value object, or just a scalar value).
Now, to enable Doctrine 2 to persist
our Customer class, with its Address value object, we will need to
tell it how to map to columns to hold each part of the address. We
could use a custom mapping type
for this, but there is another way which is probably simpler and a
little more generic.
As Address is not an entity, and has no
identifier (ie. no primary key), the address data cannot be held in a
separate table - it will have to go in the same table as the Customer
entity. We could therefore just add the fields to our Customer entity
and map them to an accessor like this (note that here we do have a
setter as well as a getter for the address, because the Customer
entity is not immutable):
namespace Entities; /** * @Entity @Table(name="customer") * @property int $id * @property string $name * @property \ValueObjects\Address $address * @property string $email_address * @property string $telephone **/ class Customer extends BaseEntity { /** @var integer @Id @GeneratedValue @Column(type="integer") */ protected $id; /** @var string @Column(type="string") */ protected $name = ''; /** @var \ValueObjects\Address */ protected $address; /** @var string @Column(type="string", length=150) **/ protected $email_address = ''; /** @var string @Column(type="string", length=40) **/ protected $telephone = ''; /** @var string @Column(type="string", length=100) **/ private $address_line_1 = ""; /** @var string @Column(type="string", length=100) **/ private $address_line_2 = ""; /** @var string @Column(type="string", length=100) **/ private $address_line_3 = ""; /** @var string @Column(type="string", length=100) **/ private $address_town = ""; /** @var string @Column(type="string", length=100) **/ private $address_state = ""; /** @var string @Column(type="string", length=100) **/ private $address_postcode = ""; /** * @ManyToOne(targetEntity="Country", fetch="EAGER") * @JoinColumn(name="address_country", referencedColumnName="code") * @var Country **/ private $address_country; public function setAddress(\ValueObjects\Address $address) { $this->address = $address; $this->address_line_1 = $address->line_1; $this->address_line_2 = $address->line_2; $this->address_line_3 = $address->line_3; $this->address_town = $address->town; $this->address_state = $address->state; $this->address_postcode = $address->postcode; $this->address_country = $address->country; } /** @return \ValueObjects\Address **/ public function getAddress() { if (!isset($this->address)) //Lazy load { $this->address = new ValueObject\Address( $this->address_line_1, $this->address_line_2, $this->address_line_3, $this->address_town, $this->address_state, $this->address_postcode, $this->address_country); } return $this->address; } }
Here we have private properties to map
the individual parts of the address to database columns without
exposing these to the outside world (the magic getter in the base
class will not have access to them), but we have a public getter and
setter for the address value object.
This will work, but it doesn't look
very nice - our Customer class is now bloated with address mapping
code. Also, we might have other entities that also need an address,
and we don't want to have to copy and paste this all over the place.
This is where traits come in handy! We can use a trait to keep the
nasty mapping kludge out of our entity, and re-use it wherever an
address is needed:
namespace Traits; /** * This trait allows us to map address value objects to database columns using Doctrine 2 */ trait Address { /** @var string @Column(type="string", length=100) **/ private $address_line_1 = ""; /** @var string @Column(type="string", length=100) **/ private $address_line_2 = ""; /** @var string @Column(type="string", length=100) **/ private $address_line_3 = ""; /** @var string @Column(type="string", length=100) **/ private $address_town = ""; /** @var string @Column(type="string", length=100) **/ private $address_state = ""; /** @var string @Column(type="string", length=100) **/ private $address_postcode = ""; /** * @ManyToOne(targetEntity="Country", fetch="EAGER") * @JoinColumn(name="address_country", referencedColumnName="code") * @var Country **/ private $address_country; public function setAddress(\ValueObjects\Address $address) { $this->address = $address; $this->address_line_1 = $address->line_1; $this->address_line_2 = $address->line_2; $this->address_line_3 = $address->line_3; $this->address_town = $address->town; $this->address_state = $address->state; $this->address_postcode = $address->postcode; $this->address_country = $address->country; } /** @return \ValueObjects\Address **/ public function getAddress() { if (!isset($this->address)) //Lazy load { $this->address = new ValueObject\Address( $this->address_line_1, $this->address_line_2, $this->address_line_3, $this->address_town, $this->address_state, $this->address_postcode, $this->address_country); } return $this->address; } }
Putting the ugly stuff there in the
trait means that the only pollution in our entity class is a use
statement:
namespace Entities; /** * @Entity @Table(name="customer") * @property int $id * @property string $name * @property \ValueObjects\Address $address * @property string $email_address * @property string $telephone **/ class Customer extends BaseEntity { use \Traits\Address; //Allows Doctrine 2 to map the Address value object /** @var integer @Id @GeneratedValue @Column(type="integer") */ protected $id; /** @var string @Column(type="string") */ protected $name = ''; /** @var \ValueObjects\Address */ protected $address; /** @var string @Column(type="string", length=150) **/ protected $email_address = ''; /** @var string @Column(type="string", length=40) **/ protected $telephone = ''; }
The fly in the ointment
Aside from the fact that manually
mapping fields is a bit of a kludge in itself, there is another
problem here. What about cases where an entity needs more than one
address? Perhaps a billing address and shipping address for example?
We can't include the trait twice (well, we could use aliasing, but both would refer to the same values and would require a monumental kludge to differentiate), so we would have to add more traits
for the different address types. I still think that is better than
putting it all directly in the entity class, but it does smell a bit
iffy. What do you think? Is there a better way?
Update: Using Embeddables
If you can use Doctrine 2.5, this problem is easily solved using Embeddables. Here is how you would do the mapping for a customer address in Doctrine 2.5:namespace ValueObjects; /** @Embeddable **/ class Address extends BaseValueObject { /** @var string **/ protected $line_1 = ""; /** @var string **/ protected $line_2 = ""; /** @var string **/ protected $line_3 = ""; /** @var string **/ protected $town = ""; /** @var string **/ protected $state = ""; /** @var string **/ protected $postcode = ""; /** @var \Entities\Country **/ protected $country; public function __construct($line_1 = '', $line_2 = '', $line_3 = '', $town = '', $state = '', $postcode = '', \Entities\Country $country = null) { $this->line_1 = $line_1; $this->line_2 = $line_2; $this->line_3 = $line_3; $this->town = $town; $this->state = $state; $this->postcode = $postcode; $this->country = $country; } }
namespace Entities; /** * @Entity @Table(name="customer") * @property int $id * @property string $name * @property \ValueObjects\Address $address * @property string $email_address * @property string $telephone **/ class Customer extends BaseEntity { /** @var integer @Id @GeneratedValue @Column(type="integer") */ protected $id; /** @var string @Column(type="string") */ protected $name = ''; /** @var \ValueObjects\Address @Embedded(class="\ValueObjects\Address") */ protected $address; /** @var string @Column(type="string", length=150) **/ protected $email_address = ''; /** @var string @Column(type="string", length=40) **/ protected $telephone = ''; }Painless!