Domain-Driven Design – The Factory in PHP

EDIT: Added usage example in the modified TimeSpanFactory.
In Domain-Driven design we want our domain model to be bullet proof. Sometimes it is necessary to ensure some business rules while bringing a new object into life. If this construction is too complicated or simply cannot be ensured by the object itself, than you should move the construction of this object in a dedicated class: A factory.
Let’s make a quick example. Let’s say we have a TimeSpan value object. Our initial implementation of a TimeSpan may look like this:
= $until ) {
throw new \InvalidArgumentException(‘Invalid time span.’);
}
$this->from = $from;
$this->until = $until;
}

// Some other useful stuff goes in here…
}

Let’s say we don’t want the TimeSpan to be unlimited, there is a maximum that can be specified by some user through a Configuration that looks like this:
<?php

class TimeSpanConfiguration
{
/** @var \DateInterval **/
private $maxTimeSpan;

public function __construct(\DateInterval $maxTimeSpan)
{
$this->maxTimeSpan = $maxTimeSpan;
}
}

So now we have a business rule that states: There can be no TimeSpan longer than the configured maximum. How do we enforce that? We can construct TimeSpan instances as we like, nothing will ever enforce that configuration. Well there are – as always – multiple solutions to this problem. The one that I want to show you is the Factory:
<?php

class TimeSpanFactory
{
/** @var TimeSpanConfiguration **/
private $maxTimeSpan;

public function __construct(TimeSpanConfiguration $timeSpanConfiguration)
{
// For the sake of simplicity we just give the factory the configuration directly.
$this->timeSpanConfiguration = $timeSpanConfiguration;
}

public function createTimeSpan(\DateTimeImmutable $from, \DateTimeImmutable $until): TimeSpan
{
// We just ask the configuration if the given from-until time span is valid.
// That way we don’t need any getters on the configuration. Neat.
if ( !$this->timeSpanConfiguration->isValidTimeSpanFromUntil($form, $until) ) {
throw new \DomainException(‘This time span is too long!’);
}

return new TimeSpan($from, $until);
}

}

Now, when constructing a new TimeSpan, we just use the TimeSpanFactory instead of the constructor of TimeSpan directly. This way we always get a valid TimeSpan that is not longer than the configured maximum.
You may ask now: Well, I could still construct an invalid TimeSpan when using the constructor directly. And YES, you are right! This may be a problem depending on your team. If your team is small enough, you could just agree on always using the TimeSpanFactory instead of the constructor directly. But there is a solution that enforces you to use the Factory: Reflection. This may introduce some side effects you don’t want, but I will show you anyway 😉
First, make the constructor of TimeSpan private:
<?php

class TimeSpan
{
/** @var \DateTimeImmutable **/
private $from;

/** @var \DateTimeImmutable **/
private $until;

private function __construct(\DateTimeImmutable $from, \DateTimeImmutable $until)
{
if ( $from >= $until ) {
throw new \InvalidArgumentException(‘Invalid time span.’);
}
$this->from = $from;
$this->until = $until;
}

// Some other useful stuff goes in here…
}

Now, there is no way to construct a TimeSpan with a simple new. PHP will throw a fatal error when you try to do that. Well but if you can’t construct it anymore, how can our TimeSpanFactory construct it then? Reflection to the rescue! Let’s look at our new implementation of TimeSpanFactory:
<?php

class TimeSpanFactory
{
/** @var TimeSpanConfiguration **/
private $maxTimeSpan;

public function __construct(TimeSpanConfiguration $timeSpanConfiguration)
{
// For the sake of simplicity we just give the factory the configuration directly.
$this->timeSpanConfiguration = $timeSpanConfiguration;
}

public function createTimeSpan(\DateTimeImmutable $from, \DateTimeImmutable $until): TimeSpan
{
// We just ask the configuration if the given from-until time span is valid.
// That way we don’t need any getters on the configuration. Neat.
if ( !$this->timeSpanConfiguration->isValidTimeSpanFromUntil($form, $until) ) {
throw new \DomainException(‘This time span is too long!’);
}

return $this->constructTimeSpan($from, $until);
}

private function constructTimeSpan(\DateTimeImmutable $from, \DateTimeImmutable $until): TimeSpan
{
$class = new ReflectionClass(TimeSpan::class);
$constructor = $class->getConstructor();
$constructor->setAccessible(true);
$timeSpan = $class->newInstanceWithoutConstructor();
$constructor->invoke($timeSpan, $from, $until);

return $timeSpan;
}

}

// Usage:
$factory = new TimeSpanFactory(new TimeSpanConfiguration(new \DateInterval(‘PT2D’)));

$timeSpan = $factory->constructTimeSpan(new \DateTimeImmutable(‘2019-02-17 17:00:00’), new \DateTimeImmutable(‘2019-02-17 18:00:00’));

// Fails due too to long time span
$timeSpan = $factory->constructTimeSpan(new \DateTimeImmutable(‘2019-02-17 17:00:00’), new \DateTimeImmutable(‘2019-02-17 23:00:00’));

// Also failes, but due to private constructor 😉
$timeSpan = new TimeSpan(new \DateTimeImmutable(‘2019-02-17 17:00:00’), new \DateTimeImmutable(‘2019-02-17 23:00:00’));

Wa…

I agree, but I rather have a bullet proof domain model than the chance that someone constructs an invalid TimeSpan that breaks the business rule. This may be a junior developer that doesn’t know that you should use the TimeSpanFactory, or even a senior that just has forgotten about the Factory.
But – as I said – that depends on your team.
Happy DDD’ing!
PS: I wrote this post in less than 10 minutes, all samples are written from scratch within the markdown editor of dev.to, so please: If you find any bug, typo etc., let me know 🙂

Link: https://dev.to//coajaxial/domain-driven-design—the-factory-in-php-4gea