rfc:new_in_initializers

This is an old revision of the document!


PHP RFC: New in initializers

Introduction

This RFC proposes to allow use of new expressions inside initializer expressions, including for property and parameter default values.

Currently, code such as the following is not permitted:

class Test {
    public function __construct(
        private Logger $logger = new NullLogger,
    ) {}
}

Instead, it is necessary to write code along the following lines:

class Test {
    private Logger $logger;
 
    public function __construct(
        ?Logger $logger = null,
    ) {
        $this->logger = $logger ?? new NullLogger;
    }
}

This makes the actual default value less obvious (from an API contract perspective), and requires the use of a nullable argument.

This RFC proposes to relax this restriction and allow the use of new inside all initializer expressions.

Proposal

new expressions are allowed as part of initializer expressions. It is possible to pass arguments to the constructor, including the use of named arguments:

// All allowed:
function test(
    $foo = new A,
    $bar = new B(1),
    $baz = new C(x: 2),
) {
}

The use of a dynamic or non-string class name is not allowed. The use of argument unpacking is not allowed. The use of unsupported expressions as arguments is not allowed.

// All not allowed (compile-time error):
function test(
    $foo = new (CLASS_NAME_CONSTANT)(), // dynamic class name
    $bar = new A(...[]), // argument unpacking
    $baz = new B($abc), // unsupported constant expression
) {}

Affected positions are static variable intializers, constant and class constant initializers, static and non-static property intializers, parameter default values, as well as attribute arguments:

static $x = new Foo;
 
const C = new Foo;
 
#[AnAttribute(new Foo)]
class Test {
    const C = new Foo;
    public static $prop = new Foo;
    public $prop = new Foo;
}
 
function test($param = new Foo) {}

Evaluation of expressions

Static variable initializers, constant and class constant initializers, and static property initializers are evaluated once per request. For constants the evaluation occurs when they are declared, for other cases evaluation is lazy and the exact time of evaluation is not specified.

Currently, all initializers in a class (and its parents) are evaluated together on the first use of the class that may require evaluated initializers (e.g. object instantiation, class constant access or static property access). However, this is not guaranteed behavior, and code should not rely on a specific point of evaluation.

For non-static property default values, parameter default values and attribute arguments, evaluation happens as-if the expression is evaluated on each object instantiation, each function call, or each attribute instantiation respectively. Any side-effects are observable for each evaluation.

If the construction of an object fails because the evaluation of a property default value resulted in an exception, then the destructor for the object will not be called. This matches the behavior for exceptions thrown from the constructor.

If the evaluation of an object property default value results in recursion, an Error exception is thrown:

class Test {
    public $test = new Test;
}
 
new Test;
// Error: Trying to recursively instantiate Test while evaluating default value for Test::$test

Backward Incompatible Changes

None.

Future Scope

This RFC is narrow in that it only adds support for new expressions. However, it also lays the technical groundwork for supporting other expressions like calls.

Vote

Yes/No.

rfc/new_in_initializers.1614782407.txt.gz · Last modified: 2021/03/03 14:40 by nikic