
Today we are going to talk about a practice that will visibly improve the quality and readability of our code.
Description
Semantic constructors or named constructors is a small refactor that consists of eliminating the complexity of constructors when they involve having to know implementation details of the constructor.
Let's take for example the following code:
<?php
class Price
{
private $amount;
private $currency;
public function __construct(float $amount, string $currency)
{
$this->checkAmount($amount);
$this->checkCurrency($currency);
$this->amount = $amount;
$this->currency = $currency;
}
private function checkAmount(float $amount)
{
if (0 >= $amount) {
throw new PriceMustBePositiveException();
}
}
private function checkCurrency(string $currency)
{
$validCurrencies = ['EUR', 'USD, 'GBP'];
if (!in_array($validCurrencies, $currency)) {
throw new InvalidCurrencyException();
}
}
public function getAmount() : float
{
return $this->amount;
}
public function getCurrency() : string
{
return $this->currency;
}
}Here we see a small Value Object which represents a price. The value object is composed of two attributes, the quantity and the currency to use.
As it is set up, this value object forces us to know details of its implementation, such as which currencies to support and which not to support.
Implementation
To apply the refactor of named constructors we only have to follow a few steps:
- Make the constructor private
- Create one or more static methods that, using the constructor in the appropriate way, return a new object.
- Give the new static methods a name that describes their intentionality.
- Replace the old calls to the constructor with calls to the new static methods
- Check that the tests pass.
Let's see an example of this refactor:
<?php
class Price
{
private $amount;
private $currency;
private function __construct(float $amount, string $currency)
{
$this->checkAmount($amount);
$this->checkCurrency($currency);
$this->amount = $amount;
$this->currency = $currency;
}
private function checkAmount(float $amount)
{
if (0 >= $amount) {
throw new PriceMustBePositiveException();
}
}
private function checkCurrency(string $currency)
{
$validCurrencies = ['EUR', 'USD, 'GBP'];
if (!in_array($validCurrencies, $currency)) {
throw new InvalidCurrencyException();
}
}
public static function createPriceInPounds($amount)
{
return new self($amount, 'GBP');
}
public static function createPriceInDollars($amount)
{
return new self($amount, 'USD');
}
public static function createPriceInEuros($amount)
{
return new self($amount, 'EUR');
}
public function getAmount() : float
{
return $this->amount;
}
public function getCurrency() : string
{
return $this->currency;
}
}As you can see, the constructor has become private and 3 new methods have appeared:
- createPriceInDollars
- createPriceInPounds
- createPriceInEuros
And using them is that simple:
$poundsPrice = Price::createPriceInPounds(22.5);
$dollarsPrice = Price::createPriceInDollars(22.5);
$eurosPrice = Price::createPriceInEuros(22.5);
Now, the public interface of the value object exposes the different ways of creating a price that it supports, so the programmer does not have to know the internal details of its implementation.
Conclusion
This is an implementation variant of the Factory Method pattern, in which the constructor method is implemented in the same class and must therefore be static.
Its application is really simple, and the advantages are evident. Since we are using static methods to build the objects, it could be thought that the testability of these classes would be compromised, but due to the scope of use of the same does not usually generate problems. Let me explain: to test these methods it is not required (normally) the use of mocks or stubs of the static method, which is the most critical thing that could happen to us.
To test these methods we can simply rely on the fact that:
- Return an object of the appropriate class
- Exceptions are thrown if it is the case that they should be thrown.
- The information we have passed to the constructor is returned correctly when accessing the appropriate attributes via getters
There is another use case in which the named constructors are useful, and it is when we want to have more than one constructor, obviously receiving different parameters. This in languages that support constructor overloading such as Java is very easy to do, but PHP does not allow this.
The solution is to create new semantic constructors that allow us to implement different constructors with different parameters.
Use case with Amount
<?php
class Amount
{
private $amount;
public function __construct(float $amount)
{
$this->amount = $amount;
}
public function getAmount()
{
return $this->amount;
}
}Here we have incorporated a second value object Amount which holds only a float value.
If we wanted our value object Price could be built from an object Amount, In PHP we can't do constructor overloading to add a second constructor that instead of a float receive as parameter a Amount, so the solution is to add the methods:
- createPriceInPoundsFromAmount
- createPriceInDollarsFromAmount
- createPriceInEurosFromAmount
As can be seen, the name of the method still clearly reflects its intentionality.
Find out more at our blog.
