Named constructor

How to create an instance ?

Classical OOP

In classical OOP approach, you will use the __construct magic method to do the job. Let's reuse our Ticket entity described in SymfonyWorkflow journey - part 1

/**
 * All attributes protected, stick to SOLID principles    
 */
class Ticket
{       
    protected $id;

    protected $title;

    /**
    * Classical constructor magic method
    */
    public function __construct()
    {

    }

}

Note : I cleared unnecessary stuff, such as getters/setters, annotations... So the rest look much more like a value object.

So far, so good. What's wrong with this? Nothing technically speaking, this does the job. But what if the object is complex, and you have more than one way to create instance (from a business POV) ? This is where named constructors come into light.

Named constructor

This is mostly a kind of static factory (a system to create an object) embedded in object class itself. Above example become :

```php /** * All attributes protected, stick to SOLID principles
*/ class Ticket {
protected $id;

 protected $title;

 /**
 * Constructor becomes protected to ensure there is direct call
 */
 protected function __construct()
 {

 }

 /**
 * Named constructor example. Static to be called from class itself
 */
 public static function fromTitle(string $title): self
 {
    $ticket = new static();
    $ticket->setTitle($title);
    return $ticket;  
 } 

} ```

So instead of doing $ticket = new Ticket() you would do $ticket = Ticket::fromTitle('new title').

Notes : - _Pay attention to self return type. This allow not to hardcode the class return type, hence offers dynamism on override (with precautions though). - _In the same way, the new static() allow to instantiate current object instance. In case you extended Ticket, you will obtain here the last-in-chain class type.

Advantages and... advantages

Not really drawbacks here, let's focus on the advantages

 Nice semantics

$ticket = Ticket::fromTitle('new title') speaks for itself. More than creating an instance, you describe the intent which is a key part in complex applications.

Override constructors

Unlike other languages (such as C++) , PHP can only have one constructor per class. If you stick to your class to get an instance, the only way to fulfill different instance creation scenarii is to:

  • Low the signature exigence (parameters type-hinting, nullable,...)
  • Creates several checks and tests to ensure params passed on creation are still consistent

As named constructors are simple static methods, and considering the fact that __construct() is no more available from outside world, you can create as much named constructors as you want. And guess what ?

  • Design intents are still here (Ticket::fromTitle(), Ticket::fromAnotherTicket()...).
  • All coherence checks are separated and lies only in named constructor they belong to.

Last but not least, this is totally framework agnostic. You can start using them now, with nothing but intention.

Tags: php, oop, semantic, ddd