Some Thoughts on PHP's DateTime, Object Mutability and an Alternative Implementation
I'm beginning think that while the introduction of PHP's newish DateTime
object is very welcome, its implementation is one of the big missed opportunities in the language. This is because the decision was taken to make it mutable. In essence, DateTime has been implemented as an Entity rather than a Value Object.
Value Objects
This is kind of odd, because dates and times are almost the canonical example of a Value Object, and this is how most languages implement them. I'll let Martin Fowler explain it better than I can:
For value objects to work properly...it's a very good idea to make them immutable - that is, once created none of their fields change. The reason for this is to avoid aliasing bugs. An aliasing bug occurs when two objects share the same value object and one of the owners changes the values in it.
Thus, if Martin has a hire date of March 18 and we know that Cindy was hired on the same day, we may set Cindy's hire date to be the same [object] as Martin's. If Martin then changes the month in his hire date to May, Cindy's hire date changes too. Whether it's correct or not, it isn't what people expect.
Usually with small values like this people expect to change a hire date by replacing the existing date object with a new one. Making Value Objects immutable fulfills that expectation.
So by choosing to make DateTime
mutable, you break people's expectations about how values work, and increase the risk of introducing obscure bugs.
You also lose the option of employing a lot of patterns that apply to Value Objects, such as Flyweight, which allows us to optimise by replacing large numbers of similar objects with references to a small number of shared Value Objects.
DateTime Arithmetic
Perhaps more practically, in PHP at least, this mutability leads to some very clunky syntax when calculating new dates based on intervals. So given a DateTime
named $startdate
and a DateInterval
named $interval
, here's how I'd like to be able to calculate an end date:
<?php
$enddate = $startdate->add($interval);
In this way, $startdate
would be safely unmodified, whilst $enddate
would be a new DateTime
object. But in actual fact, DateTime::add()
and DateTime::sub()
modify the original object.
Here's a kind of fiddly workaround:
<?php
$enddate = clone($startdate);
$enddate->add($interval);
And you sort of have to hope that nobody uses $enddate
in between, and that nobody tampers with $startdate
elsewhere in the code. There may be better ways of doing this, so do comment if there are.
ImDateTime
For fun, I've started to put together an immutable alternative to DateTime
, which implements the syntax that I'd prefer to see, and which I've named ImDateTime
. It isn't exactly production-ready, or even finished, but it's there on GitHub for people to have a play with.
Simon Harris
Thanks, David.
I should really update that point, as the code is ready for use now. I'd be happy for people to use it, make suggestions and even send pull requests.
Ciaran McNulty
For anyone who finds this on Google, PHP 5.5 now contains a DateTimeImmutable object that operates much like Simon describes here.
David Allen
HI there,
We are thinking of implementing a similar object but I thought I would first google and see what I came up with.
Your object looks good.
However, you wrote it is not production ready.
What did you mean by that?
Regards
David