What this code does?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class DateTimeInterval { private $from; private $to; private $allDayLong; public function setFrom(\DateTime $from) { if ($this->isAllDayLong()) { $from->setTime(0, 0, 0); } $this->from = $from; return $this; } public function getFrom() { ... } // omitted public function setTo(\DateTime $to) { if ($this->isAllDayLong()) { $to->setTime(23, 59, 59); } $this->to = $to; return $this; } public function getTo() { ... } // omitted public function allDayLong() { $this->allDayLong = true; } public function isAllDayLong() { return $this->allDayLong; } } |
It’s a simple class that represent a DateTimeInterval
with a flag named allDayLong
that let an object of DateTimeInterval
class to fill all day.
You can see that setFrom
and setTo
are affected from allDayLong
flag: if is set, time of these DateTime
are changed in order to fill all day.
But what happens when you don’t (can’t) control directly the order of setters calls? For example, if this entity is managed from other service (i.e.: symfony form type) and you can’t predict (trust) setters alternation? What happen if allDayLong
is called after other setters? As you can imagine, object is not consistent as from
and to
has not the proper values.
Moreover this object represent a DateTimeInterval
and is pretty senseless to create it without from
and to
interval bounds.
Value objects
To avoid setters order problems and object instantiation without any data, change DateTimeInterval
class to be a value object and to avoid setters (as this kind of objects should be immutable)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class DateTimeInterval { private $from; private $to; private $allDayLong = false; public function __construct(\DateTime $from = null, \DateTime $to = null, $allDayLong = false) { if (!$from) { $this->from = new \DateTime(); } if (!$to) { $this->to = new \DateTime(); } if ($allDayLong) { $this->from->setTime(0, 0, 0); $this->to->setTime(23, 59, 59); $this->allDayLong = true; } } public function getFrom() { ... } // omitted public function getTo() { ... } // omitted public function isAllDayLong() { return $this->allDayLong; } } |
To recap
- Don’t need to care about setters order (of calls)
- Entity is valid from instantiation to persist/unset
- Changes to this object are not possibile as it’s a value object; if you need to change it, create a brand new one
Everything said there is valid and a good practice not only for this particular type of class, but whenever you need to check other members and you don’t want to rely on setters order of calls and whenever you are forced to call setters right after object instantiation just to make it “valid” (or initialized). Of course it’s not always possible to remove setters because they can have sense somewhere: you should never take a solution as a “pattern” and apply it here and there without evaluating your scenario.
Soon we will write and publish a post with an example of value object and symfony form. Stay tuned!