I’m a big fan of Value Objects, as they are very helpful in encapsulating behavior, and communicating intent. In fact, as a
friend remarked, Value Objects are the heart and soul of Object Oriented Programming. Discussing the uses of Value Objects
is not what I want to talk about here though.
Let’s set up two simple examples first:
<?php
class TwitterHandle
{
private $handle;
public function __construct($handle)
{
// Put some validation here, like a regex check
$this->handle = $handle;
}
}
class DateRange
{
private $start, $stop;
public function __construct(DateTime $start, DateTime $end)
{
// Put some validation here, like checking if $end > $start
$this->start = $start;
$this->stop = $end;
}
}
Simple string casting
Value Objects are one of the few places where using PHP’s magic __toString()
makes perfect sense. For single-value Value Objects like the TwitterHandle
, it’s obvious what should be in there. The
_toString() method simply returns the string representation.
<?php
class TwitterHandleTest extends PHPUnit_Framework_TestCase
{
/** @test */
public function ItShouldCastToString()
{
$twitterHandle = new TwitterHandle('@mathiasverraes');
$this->assertEquals(
'@mathiasverraes',
(string) $twitterHandle,
"Casting a TwitterHandle to a string "
."should return the string version of the handle"
);
}
}
Implemented inside TwitterHandle, it looks like this:
<?php
class TwitterHandle
{
private $handle;
// constructor ....
public function __toString()
{
return $this->handle;
}
}
That’s quite self-evident, but what about other cases?
Rules for casting Value Objects to string
I had a couple of basic rules, that I have been using for a while for Value Objects. They were always implicit, but
talking about them in a pair programming session made them explicit.
1. Don’t use _toString() for presentation.
It’s tempting to use a __toString() method to render the Value Object in a View. More often
than not, you would need to put presentation logic in your Value Object, and that’s a big no-no. It’s better to perform
presentation rendering in a separate class or function, like a filter for your templating engine. It allows you to fine-tune
the rendering for your use case.
<span>
Find me on Twitter:
<?php renderAsTwitterLink($user->getTwitterAccount()) ?>
</span>
<!-- renders to: -->
<span>
Find me on Twitter:
<a href="https://twitter.com/mathiasverraes" title="Visit Twitter">
@mathiasverraes
</a>
</span>
Imagine we would have put the HTML rendering inside the TwitterHandle class; that would have been a sackable offense in
my book! You’ll notice that this is especially important with Value Objects like dates and monetaries, where the
rendering depends on the language.
2. Always return a complete representation of the Value Object.
Another temptation is to use string casting to get the numeric part of a measurement.
<?php
$aDistance = new Distance(5, 'meters');
$this->assertEquals('5', (string) $aDistance); // bad, don't do this
'5'
is not a valid representation of a distance. Is it meters, centimeters, or inches? That information is now lost. If you start doing
calculations with that '5'
, you may get nasty bugs. The kind of bugs that crash spacecrafts.
So make sure that the string represents all of the Value Object:
<?php
$aDistance = new Distance(5, 'meters');
$this->assertEquals('5 meters', (string) $aDistance); // much better
3. Return a parsable string
The most useful string representation you can come up with, is one that can be parsed back into the Value Object. For the
TwitterHandle example, that’s again very easy, so let’s use DateRange as an example:
<?php
$aDateRange = new DateRange(
new DateTime('2013-01-01'),
new DateTime('2013-02-07')
);
$stringVersion = (string) $aDateRange;
$this->assertEquals('2013-01-01 - 2013-02-07', $stringVersion);
$parsedDateRange = DateRange::parse($stringVersion);
$this->assertEquals($aDateRange, $parsedDateRange);
So as you can see, '2013-01-01 - 2013-02-07'
is a string representation of a DateRange, that can be parsed back into a DateRange object.
In pseudocode:
DateRange == parse(string(DateRange))
It works just as well for our other examples:
<?php
$this->assertEquals(
new Distance(5, 'meters'),
Distance::parse(
(string) new Distance(5, 'meters')
)
);
// leaving the implementation as an exercise for the reader :-)
This will turn out to be very handy, especially when integrating with other systems, like a REST api or a database. But
that’s something we’ll cover in a future blog post.
Related posts: