Naming constructors in PHP
The chances are that you are already aware of the concept of named constructors. If not, take a look at Matthias Verraes' excellent article Named Constructors in PHP.
When it comes to consistently naming constructors, I currently apply the following rules for different types of objects.
Services
When a service requires dependencies, use the default __construct()
method for instantiation.
<?php
declare(strict_types=1);
namespace Example;
final class AccountService
{
public function __construct(
private readonly AccountEventStore $accountEventStore,
private readonly AccountEventDispatcher $accountEventDispatcher,
) {
}
// ...
}
Why? Typically, a service has a single way of construction. Consequently, it only needs a single constructor.
During yesterday's Office Hours of The PHP Consulting Company, Stefan Priebsch shared that he experimented with named constructors for services. Stefan marks the __construct()
method as private
and uses a named constructor collaboratingWith()
instead, which sounds like an idea that I might give a try.
<?php
declare(strict_types=1);
namespace Example;
final class AccountService
{
private function __construct(
private readonly AccountEventStore $accountEventStore,
private readonly AccountEventDispatcher $accountEventDispatcher,
) {
}
public static function collaboratingWith(
AccountEventStore $accountEventStore,
AccountEventDispatcher $accountEventDispatcher,
): self {
return new self(
$accountEventStore,
$accountEventDispatcher,
);
}
// ...
}
Stefan also pointed out that marking the __construct()
method as private
and using a named constructor instead has the advantage that developers cannot invoke the __construct()
method on an already instantiated object, which is technically possible. Stefan notes that an RFC proposing to disallow multiple constructor calls has become inactive. Hence, marking the __construct()
method as private
and using a named constructor takes away one more possibility for a PHP developer to shoot themselves in the foot.
Following our conversation, Stefan has published How do you name constructors?, where he provides additional examples, explanations, and reasoning.
Exceptions
When implementing a custom exception, allow instantiation via a named constructor that suits the purpose, never override the default constructor.
<?php
declare(strict_types=1);
namespace Example;
use Doctrine\ORM;
final class CouldNotFindUser extends \RuntimeException
{
public static function identifiedBy(UserId $userId): self
{
return new self(\sprintf(
'Could not find a user identified by "%s".',
$userId->toString(),
));
}
public static function withEmailAddress(EmailAddress $emailAddress): self
{
return new self(\sprintf(
'Could not find a user with email-address "%s".',
$emailAddress->toString(),
));
}
}
Why? Depending on the scenario, you may want to throw an exception of the same type, but based on different inputs. Named constructors allow doing just that. When you override the default constructor, it will be harder for cases where you are not ready to extract a named constructor yet or want to pass in an exception code, a previous exception, or both during the construction of the exception.
Entities
When an ORM entity requires other entities or values, use the default __construct()
method for instantiation.
In a typical project using the Doctrine ORM an entity also has only a single way of construction. I have chosen to use the default __construct()
method for this kind of entity.
<?php
declare(strict_types=1);
namespace Example;
use Doctrine\ORM;
#[ORM\Mapping\Entity]
#[ORM\Mapping\Table(name="user_reset_password_token")]
class UserResetPasswordToken
{
#[ORM\Mapping\Column(
length: 255,
name: 'token',
nullable: false,
type: 'example_token',
unique: true,
)]
#[ORM\Mapping\Id]
private Token $token;
#[ORM\Mapping\JoinColumn(
name: 'user_id',
nullable: false,
onDelete: 'CASCADE',
referencedColumnName: 'id',
unique: true,
)]
private User $user;
#[ORM\Mapping\Column(
name: 'expires_at',
nullable: false,
type: 'datetime_immutable',
)]
private \DateTimeImmutable $expiresAt;
public function __construct(
private Token $token,
private User $user,
private \DateTimeImmutable $expiresAt,
) {
}
// ...
}
Although I am currently still working on projects using the Doctrine ORM, I have become more interested in working on projects using message-driven architectures.
For an event-sourced entity, mark the default __construct()
method as private
, add a named constructor create()
that initiates the lifecycle of the entity, and add a named constructor fromEvents()
that allows reconstituting the entity from a list of events.
<?php
declare(strict_types=1);
namespace Example;
final class Order
{
/**
* @var array<int, OrderEvent>
*/
private array $events = [];
private function __construct(OrderEvent ...$events)
{
foreach ($events as $event) {
$this->apply($event);
}
}
public static function create(
OrderId $orderId,
OrderStartedAt $orderStartedAt,
): self {
$order = new self();
$order->record(OrderStarted::create(
$orderId,
$orderStartedAt,
));
return $orderId;
}
public static function fromEvents(OrderEvent ...$orderEvents): self
{
return new self(...$orderEvents);
}
// ...
}
Value Objects
I distinguish two three types of value objects:
- value objects wrapping a primitive value
- value objects composing primitive values
- value objects composing other value objects
Value Objects wrapping a primitive value
When a value object wraps a single primitive value, such as
- a
bool
- a
float
- an
int
- a
string
or an immutable object from the Standard PHP Library (SPL) , for example,
- an instance of
DateTimeImmutable
mark the default __construct()
method as private
and add named constructors with corresponding counterparts for accessing the wrapped value (or a representation of it).
For example, to allow instantiation of a Name
value object from a string
value, I add a constructor with the name fromString()
and a corresponding method toString()
that provides access to the wrapped string
value.
<?php
declare(strict_types=1);
namespace Example;
final class Name
{
private function __construct(private readonly string $value)
{
}
/**
* @throws \InvalidArgumentException
*/
public static function fromString(string $value): self
{
if ('' === \trim($value)) {
throw new \InvalidArgumentException('Value can not be blank or empty.');
}
return new self($value);
}
public function toString(): string
{
return $this->value;
}
}
As another example, to allow instantiation of a DateOfBirth
value object from an instance of DateTimeImmutable
, I add a named constructor fromDateTimeImmutable()
and a corresponding accessor toDateTimeImmutable()
that provides access to the wrapped DateTimeImmutable
instance.
<?php
declare(strict_types=1);
namespace Example;
final class DateOfBirth
{
private function __construct(private readonly \DateTimeImmutable $value)
{
}
public static function fromDateTimeImmutable(\DateTimeImmutable $value): self
{
return new self(\DateTimeImmutable::createFromFormat(
'!Y-m-d',
$value->format('Y-m-d'),
));
}
public function toDateTimeImmutable(): \DateTimeImmutable
{
return $this->value;
}
}
Why bother with a toDateTimeImmutable()
method when the field is already readonly
and not make the field public
instead?
From the perspective of a user, the internal value does not matter. The only thing that matters to a user is that they can create the value object from some primitive value and obtain a primitive representation of the value object when necessary. In the example above, the DateOfBirth
value object could use a string
as internal representation, three int
s, or three string
s. If I exposed the internal representation directly by making the field public
, I would need to change every place that references the field(s). By encapsulating the primitive entirely, I am still open to changing the internal representation without making these changes anywhere else.
When it later turns out that I want to construct a DateOfBirth
from a string
value, I can easily add a constructor with the name fromString()
, and if need be, a corresponding accessor toString()
that returns an appropriate string
representation.
<?php
declare(strict_types=1);
namespace Example;
final class DateOfBirth
{
private function __construct(private readonly \DateTimeImmutable $value)
{
}
// ...
/**
* @throws \InvalidArgumentException
*/
public static function fromString(string $value)
{
try {
$parsed = \DateTimeImmutable::createFromFormat(
'!Y-m-d',
$value,
);
} catch (\Exception) {
throw new \InvalidArgumentException(\sprintf(
'Value "%s" does not appear to be a valid date of birth.',
$value,
));
}
// ...
return new self($parsed);
}
public function toString(): string
{
return $this->value->format('Y-m-d');
}
}
Value objects composing primitive values
I am still on the fence about whether a value object that composes more than one primitive value is a good idea. Generally, I prefer value objects to wrap a single primitive value or compose other value objects.
Why? For me, value objects serve two purposes:
- traceability
- enforcing invariants
- expressive operations
For example, a legacy codebase might use an int
or a string
for representing the concept of an International Bank Account Number (IBAN). When I slowly replace usages of the int
or string
value with an Iban
type, the concept becomes traceable throughout the codebase. Before the change, the only indication I was dealing with an IBAN was the variable name, perhaps even with a different spelling. I now have a concrete type and can easily find usages with an IDE or static code analysis. In other words, at this time, without enforcing invariants or providing expressive operations, introducing a value object already has the great benefit of traceability.
Step by step, I could enforce invariants using guard clauses and add methods that allow expressive operations.
If I composed multiple primitives into a value object and cared about these primitives, then I would not benefit from traceability and expressive operations anymore.
For example, I could compose two DateTimeImmutable
s into a Duration
value object to model the duration of a specific event.
<?php
declare(strict_types=1);
namespace Example;
final class Duration
{
private function __construct(
public readonly \DateTimeImmutable $start,
public readonly \DateTimeImmutable $end,
) {
}
/**
* @throws \InvalidArgumentException
*/
public static function create(
\DateTimeImmutable $start,
\DateTimeImmutable $end,
): self {
// ...
return new self(
$start,
$end,
);
}
}
Given proper enforcement of invariants (an event can not end before it starts), the Duration
object works fine, and the concept of a Duration
is now clearly more traceable than passing around two DateTimeImmutable
s. However, if I care about the concept of a Start
and End
, and when I want to distinguish between a Start
, an End
, and an ordinary instance of DateTimeImmutable
, and want to provide expressive operations for these values, it will make more sense to introduce additional value objects.
<?php
declare(strict_types=1);
namespace Example;
final class Duration
{
private function __construct(
public readonly Start $start,
public readonly End $end,
) {
}
/**
* @throws \InvalidArgumentException
*/
public static function create(
Start $start,
End $end,
): self {
if (!$start->isBefore($end)) {
// ...
}
return new self(
$start,
$end,
);
}
}
Value objects composing other value objects
When a value object composes other value objects, mark the default __construct()
method as private
and add a named constructor create()
.
I have found that this kind of value object only has a single way of construction. However, instead of using the default __construct()
method, I prefer to use a named constructor create()
for this kind of value object.
<?php
declare(strict_types=1);
namespace Example;
final class Attendee
{
private function __construct(
public readonly Name $name,
public readonly DateOfBirth $dateOfBirth,
) {
{
}
public static function create(
Name $name,
DateOfBirth $dateOfBirth,
): self {
return new self(
$name,
$dateOfBirth,
);
}
}
Why create()
and not fromNameAndDateOfBirth()
? When I name the method fromNameAndDateOfBirth()
, and later decide to compose additional value objects, it would be consistent to reflect the change by changing the method name. However, the method name would become longer and longer, and I would like to avoid that.
Consistency
Using named constructors for value objects, even when these value objects have only a single way of construction, provides a consistent naming scheme. Consistency is key to making it easier for developers to join, understand, and contribute to a project.
Do you find this article helpful?
Do you have feedback?
Just blogged: Naming constructors.
— Andreas Möller (@localheinz) March 26, 2022
↓https://t.co/CaSf28763K