Leaking Mutability

Value objects don't have an identity. If all the properites of a value object are equal to another, the objects are interchangable. To go along with that, value objects are immutable -- they can't be changed once created. It's part of the whole if the properties are equal thing. "If two value objects are created equal, they should remain equal," to quote wikipedia.

Let's take a small code snippet:

<?php

class ReportPeriod
{
    /** @var DateTime */
    private $startDate;

    /** @var DateInterval */
    private $length;

    public function __construct(\DateTime $start, \DateInterval $length)
    {
        $this->startDate = $start;
        $this->length = $length;
    }

    public function getStartDate()
    {
        return $this->startDate;
    }

    public function getLength()
    {
        return $this->length;
    }
}

Looks great, right? A report along with its start date and the interval of time which is represents all encapsulated in a meaningful value object.

Is it immutable? Not at all. But it looks that way.

Beware Leaking Mutability

Our object itself is immutable. The start date and interval references can't be changed. They were set in the constructor and there's no methods to set them. Beyond doing some reflection magic, those private properites are innaccessible.

But the objects we return, specifically the DateTime are not immutable. We leak our state to the outside world and let others change the values if they please.

<?php
$reportPeriod = new ReportPeriod(
    new \DateTime('2015-01-01'),
    new \DateInterval('P7D')
);

echo $reportPeriod->getStartDate()->format('Y-m-d'), PHP_EOL; // 2015-01-01
$reportPeriod->getStartDate()->setDate(2014, 1, 1);
echo $reportPeriod->getStartDate()->format('Y-m-d'), PHP_EOL; // 2014-01-01!

The answer here is to return copies of the objects your value object rely on or make sure those other object are immutable themselves.

<?php
class ReportPeriod
{
    // ...

    public function getStartDate()
    {
        return clone $this->startDate;
    }
}

echo $reportPeriod->getStartDate()->format('Y-m-d'), PHP_EOL; // 2015-01-01
$reportPeriod->getStartDate()->setDate(2014, 1, 1);
echo $reportPeriod->getStartDate()->format('Y-m-d'), PHP_EOL; // 2015-01-01, whew

Same goes for any instance where a collaborating object is returned: make a copy or make sure its immutable. Don't leak internal state into the outside world.

#