Named Constructors

In PHP

About Me

Conor Smith

Senior Developer, Journal Media

@conorsmith

We are hiring

careers.thejournal.ie

A Constructor

class DisplayColour
{
    public function __construct(int $red, int $green, int $blue)
    {
        if ($red < 0 || $red > 255) {
            throw new \DomainException("Red value must be between 0 and 255");
        }

        // More guard statements...

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

A "Flexible" Constructor

class DisplayColour
{
    public function __construct(int $redOrHue, int $greenOrSat, int $blueOrLight, bool $isRgb = true)
    {
        if ($isRgb) {
            if ($redOrHue < 0 || $redOrHue > 255) {
                throw new \DomainException("Red value must be between 0 and 255");
            }
        } else {
            if ($redOrHue < 0 || $redOrHue > 360) {
                throw new \DomainException("Hue value must be between 0 and 360");
            }
        }

        // More guard statements...

        if ($isRgb) {
            $this->red = $redOrHue;
            $this->green = $greenOrSat;
            $this->blue = $blueOrLight;
        } else {
            
            // Convert HSL values to RGB values...

        }
    }
}

A "Flexibler" Constructor

class DisplayColour
{
    public function __construct(
        $redOrHueOrHexValue,
        int $greenOrSat = null,
        int $blueOrLightness = null,
        bool $isNotHsl = true
    ) {
        // Here be dragons...
    }
}

A Named Constructor

class DisplayColour
{
    public static function fromHex(string $value)
    {
        if (strlen($value) !== 6) {
            throw new \DomainException("Hex value must be 6 characters long.");
        }

        // More guard statements...

        return new self(
            $red   = hextodec(substr($value, 0, 2)),
            $green = hextodec(substr($value, 2, 2)),
            $blue  = hextodec(substr($value, 4, 2))
        );
    }
}

Another Named Constructor

class DisplayColour
{
    public static function fromHsl(int $hue, int $saturation, int $lightness)
    {
        if ($hue < 0 || $hue > 360) {
            throw new \DomainException("Hue value must be between 0 and 360.");
        }

        // More guard statements...

        // Convert HSL values to RGB values...

        return new self($red, $green, $blue);
    }
}

The Actual Constructor

class DisplayColour
{
    public static function fromRgb(int $red, int $green, int $blue)
    {
        return new self($red, $green, $blue);
    }

    private function __construct(int $red, int $green, int $blue)
    {
        if ($red < 0 || $red > 255) {
            throw new \DomainException("Red value must be between 0 and 255.");
        }

        // More guard statements...

        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
}

Same Interface,

Different Implementation

class DisplayColour
{
    public static function fromRgb(int $red, int $green, int $blue) { ... }
    public static function fromHsl(int $hue, int $saturation, int $lightness) { ... }
    public static function fromHex(string $value) { ... }

    private function __construct(int $hue, int $saturation, int $lightness)
    {
        // Guard statements...

        $this->hue = $hue;
        $this->saturation = $saturation;
        $this->lightness = $lightness;
    }
}

Why?

Readability

class DisplayColour
{
    public static function fromRgb(int $red, int $green, int $blue) { ... }

    public static function fromHsl(int $hue, int $saturation, int $lightness) { ... }

    public static function fromHex(string $value) { ... }
}
class DisplayColour
{
    public function __construct(
        $redOrHueOrHexValue,
        int $greenOrSaturation = null,
        int $blueOrLightness = null,
        bool $isNotHsl = true
    ) {
        ...
    }
}

vs

Extensibility

// Import some colour values as strings...
$bgHex = "ffe375";
$fgHex = "333";

$backgroundColour = DisplayColour::fromHex($bgHex);

// This won't work...
$foregroundColour = DisplayColour::fromHex($fgHex);

// Add a new named constructor!
$foregroundColour = DisplayColour::fromShortHex($fgHex);

Expressiveness


$colour = new DisplayColour(232, 80, 66);

$colour = DisplayColour::fromRgb(232, 80, 66);

RGB

HSL


$red = DisplayColour::fromRgb(232, 80, 66);

Real World

Examples

League\Url

use League\Url\Url;

$exampleUrl = Url::createFromUrl("http://example.com");

$currentUrl = Url::createFromServer($_SERVER);

Symfony Http Request

use Symfony\Component\HttpFoundation\Request;

$serverRequest = Request::createFromGlobals();

$testRequest = Request::create("/some-endpoint", "GET");

Carbon

use Carbon\Carbon;

$date = Carbon::createFromDate(2015, 12, 31, "Europe\Dublin");

$lunchtimeToday = Carbon::createFromTime(13, 0, 0, "Europe\Dublin");

$dbDate = Carbon::createFromFormat("Y-m-d H:i:s", "2015-12-31 13:00:00");

$date = Carbon::createFromTimestamp(1451566800);

$now = Carbon::now();

$date = Carbon::parse("Last day of December 2015");

League\Csv

use League\Csv\Reader;

$csv = Reader::createFromPath("/tmp/data.csv");

$file = new SplFileObject("/tmp/data.csv");
$csv = Reader::createFromFileObject($file);

$data = "league,csv\nsymfony,request\nnesbot,carbon";
$csv = Reader::createFromString($data, "\n");

Naming Conventions


$oneHundred = Emoji::fromCode(":100:");

echo $oneHundred->toCode(); // :100:

$date = Carbon::createFromTimestamp(1451566800);

$plusOne = Emoji::thumbsUp()

return Emoji::null();

Start Tomorrow

The next time you make a change to a Value Object in your codebase, give it a named constructor.

Questions?

Named Constructors in PHP

By Conor Smith