Disallow multiple calls to an object’s constructor to ensure that the encapsulated data cannot be mutated even if an object is meant to be immutable. The goal is it to ensure that developers can trust their intuition about how things work instead of reading the documentation and to provide helpful errors from the compiler and runtime.

PHP currently supports multiple calls to the magic __construct method of classes, this is in line with all other methods of a PHP class. This means that the following PHP code is perfectly legal. It illustrates both multiple calls to a constructor and explicit invocation of a constructor on the existing object (which in effect are multiple calls too):

class A { public function __construct ( ) { echo self :: class , "

" ; } } class B extends A { public function __construct ( ) { parent :: __construct ( ) ; echo self :: class , "

" ; parent :: __construct ( ) ; } } $b = new B ; $b -> __construct ( ) ;

Output of the above code is as follows:

A B A A B A

However, support for multiple calls to the constructor is highly unintuitive for developers and can result in subtle bugs or misuse of objects. An example would be any class that is meant to be immutable.

final class User { private $id ; public function __construct ( int $id ) { assert ( $id > 0 ) ; $this -> id = $id ; } public function isRoot ( ) : bool { return $this -> id === 1 ; } } final class Area51 { private $user ; public function __construct ( User $user ) { // No need for deep cloning since our user class is immutable. $this -> user = $user ; } public function access ( ) { if ( $this -> user -> isRoot ( ) === false ) { echo 'Not Authorized!' ; } else { echo 'Welcome to Area51!' ; } } } $user = new User ( 42 ) ; $area51 = new Area51 ( $user ) ; $area51 -> access ( ) ; // Not Authorized! $user -> __construct ( 1 ) ; $area51 -> access ( ) ; // Welcome to Area51!

As illustrated, the functionality allows breaking of the encapsulation of objects at runtime. It is true that there are many ways to achieve the same thing and that the likelihood that a developer does anything like the above by accident is very low. But there is also no argument why this requires support other than misusing the constructor of a class for things it was never intended to be used for. This is most apparent with parent::__construct() calls from child classes where the PHP language specification states that:





--- A constructor should not call its base-class constructor more than once.--- php language specification: constructors

Leaving the problem to the developers themselves. It is possible for developers to protect their objects against such unintended usage by asserting that all properties are null but this is unnecessary boilerplate code in a language that is already very verbose.

We propose that multiple calls to the constructor of an object should result in an error instead of breaking encapsulation. This means in effect that the only idiomatic way to create a new instance is via the new keyword. Child classes are only permitted to call their parent constructor once and further calls are going to result in an error too.

This means in effect that the code examples posted earlier would result in errors, however, another code example that was posted on internals as a legitimate use case for calling the constructor method directly would continue to work as is:

final class DbConnection { private $dsn ; private $initializer ; public function __construct ( string $dsn ) { $this -> dsn = $dsn ; // socket stuff happens here, much like with PDO } public function query ( string $queryString ) : array { ( $this -> initializer ) ( ) ; // irrelevant from here on return [ 'query' => $queryString , 'dsn' => $this -> dsn ] ; } public static function lazyInstance ( string $dsn ) : self { $instance = ( new ReflectionClass ( self :: class ) ) -> newInstanceWithoutConstructor ( ) ; $instance -> initializer = function ( ) use ( $dsn , $instance ) { $instance -> __construct ( $dsn ) ; $instance -> initializer = function ( ) { } ; } ; return $instance ; } } $instance = DbConnection :: lazyInstance ( 'mysql://something' ) ; var_dump ( $instance , $instance -> query ( 'SELECT * FROM foo' ) , $instance -> query ( 'SELECT * FROM bar' ) ) ;

The constructor is called once only in this example, hence, the call is permitted and only subsequent calls are going to result in an error.