rfc:syntax-to-capture-variables-when-declaring-anonymous-classes

This is an old revision of the document!


PHP RFC: Syntax to Capture Variables when Declaring Anonymous Classes

Introduction

This RFC proposes the addition of an inline syntax for lexical (captured) variables when declaring anonymous classes in PHP. The goal is to simplify anonymous class declarations and make them more concise by allowing developers to capture variables from the outer scope directly.

Proposed Syntax

The proposed syntax adds a use clause to the anonymous class declaration, which captures the specified variables from the outer scope.

new class($a, $b) use ($outer) extends Foo {
    public function getIt() {
        return $this->outer;
    }
}

This syntax is equivalent to the following:

new class($outer, $a, $b) extends Foo {
    public function __construct(public mixed $outer, $a, $b) {
        parent::__construct($a, $b);
    }
 
    public function getIt() {
        return $this->outer;
    }
}

Transformation Rules

To transform the use clause into a regular anonymous class declaration, follow these rules:

  1. For each captured variable in the use clause, add it as a parameter to the anonymous class constructor.
  2. Declare a property with the same name as the captured variable in the anonymous class.
  3. Set the default visibility of the property to public and the default type to mixed if not specified in the use clause (see below).
  4. If the anonymous class extends another class and a public or protected property with the same name exists on that parent class, the property declaration should be skipped unless the variable is explicitly aliased to a property (see below).
  5. In the constructor, assign the captured variable to the corresponding property.
  6. If the anonymous class extends another class, list all parameters of the parent constructor after the parameters coming from captured variables and call the parent constructor with them, including implicit variadic values. Parameters from the parent that have the same name as captured parameters should be listed only once on the resulting constructor.

By default, captured variables are declared as public mixed. However, the proposal allows for refining the visibility and type by using an extended syntax; it also allows for capturing by reference:

new class($a) use ($foo as private int $counter, $bar as readonly string, &$biz) extends Foo {}

The above example is equivalent to:

new class($foo, $bar, $biz, $a, $b) extends Foo {
    public function __construct(private int $counter, public readonly string $bar, public mixed &$biz, $a, $b) {
        parent::__construct($a, $b);
    }
}

Conflict Resolution

Because anonymous classes are implemented as real classes with a special name in the PHP engine, they cannot hold any state by themselves. This means that the only way to bind a value to an instance of a class is via a property. No other storage is possible at the class level because that would create a memory leak since captured values could never be released.

The proposed syntax avoids this issue by defining how a use statement could be turned into a regular constructor. If we were to allow declaring both use and __construct together, we would then need to define how to merge both. This would introduce a lot of extra cases to handle:

  • If there's no constructor, create one;
  • If there is a constructor with other arguments, merge the argument lists; since there will then be an explicit argument list to new class(), merge those lists as well;
  • Maybe different handling if those other arguments are already using constructor promotion, so merge those too;
  • If there are existing lines in the constructor body, combine those with the auto-generated assignments.

That's a lot of added complexity for a syntax that aims at reducing code boilerplate.

To skip this complexity and also because declaring a use while also declaring a constructor should save little to no boilerplate, this RFC specifies that declaring both a constructor and a use clause should raise an error.

Backward Incompatible Changes

This proposal introduces a new syntax for capturing variables in anonymous class declarations. It should not introduce any backward incompatible changes, as it does not affect existing code or syntax.

Proposed PHP Version(s)

This feature is proposed for the next minor version of PHP after the approval of this RFC, likely PHP 8.3.

RFC Impact

To Existing Extensions

There should be no impact on existing extensions.

To Opcache

There should be no impact as the proposed syntax can be transformed at compile time into an equivalent existing AST.

Open Issues

None at the moment.

Future Scope

Possibly figure out a way to allow declaring both a use and a constructor.

Voting

The voting period starts on YYYY-MM-DD and ends on YYYY-MM-DD.

Implementation

TBD

rfc/syntax-to-capture-variables-when-declaring-anonymous-classes.1681396140.txt.gz · Last modified: 2023/04/13 14:29 by nicolasgrekas