rfc:new_without_parentheses

This is an old revision of the document!


PHP RFC: new MyClass()->method() without parentheses

Introduction

The “class member access on instantiation” feature was introduced in PHP 5.4.0. Since then constants, properties and methods can be accessed on a newly created instance without an intermediate variable, but only if the new expression is wrapped in parentheses:

class MyClass
{
    public function method(): void
    {
        echo 'Hello, World!';
    }
}
 
(new MyClass())->method(); // Hello, World!
 
new MyClass()->method(); // PHP Parse error: syntax error, unexpected token "->"

The goal of this RFC is to enable the second sytax without parentheses to:

  • make coding with PHP more convenient,
  • lower the learning curve,
  • decrease the visual debt,
  • ease transition from other C-like languages that don't require parentheses (Java, C#, TypeScript).

Ambiguity?

At first glance new MyClass()->method() expression might seem ambiguous. But according to the same logic new MyClass() is also ambiguous: is it new (MyClass()) or new MyClass? However, such ambiguity does not exist: new MyClass() is interpreted as an instantiation of class MyClass with zero constructor arguments, not as instantiation of MyClass() function call result.

Here's how it is solved at the grammar level. The formula for the new expression is T_NEW class_name|new_variable|(expr) ctor_arguments, where new_variable is a variable expression without calls. Thus PHP offers 3 ways to provide a class:

// class_name
new MyClass();
 
// new_variable
new $class();
 
// (expr)
new (trim(' MyClass '))();

This guarantees that the new expression is unambiguous: once the parser encounters T_NEW, it considers whatever comes next a class name. Hence it's safe and unambiguous to further use the new expression on its own without parentheses. It's like refactoring (MyClass::new())->method() to MyClass::new()->method(), nobody will ever read it as MyClass::(new())->method() or MyClass::(new()->method()).

Proposal

This RFC allows to omit parentheses around new expressions with constructor arguments' parentheses:

class MyClass
{
    const CONSTANT = 'constant';
    public static $staticProperty = 'staticProperty';
    public static function staticMethod(): string { return 'staticMethod'; }
    public $property = 'property';
    public function method(): string { return 'method'; }
    public function __invoke(): string { return '__invoke'; }
}
var_dump(
    new MyClass()::CONSTANT,        // string(8)  "constant"
    new MyClass()::$staticProperty, // string(14) "staticProperty"
    new MyClass()::staticMethod(),  // string(12) "staticMethod"
    new MyClass()->property,        // string(8)  "property"
    new MyClass()->method(),        // string(6)  "method"
    new MyClass()(),                // string(8)  "__invoke"
);
 
$myClass = MyClass::class;
var_dump(
    new $myClass()::CONSTANT,        // string(8)  "constant"
    new $myClass()::$staticProperty, // string(14) "staticProperty"
    new $myClass()::staticMethod(),  // string(12) "staticMethod"
    new $myClass()->property,        // string(8)  "property"
    new $myClass()->method(),        // string(6)  "method"
    new $myClass()(),                // string(8)  "__invoke"
);
 
var_dump(
    new (trim(' MyClass '))()::CONSTANT,        // string(8)  "constant"
    new (trim(' MyClass '))()::$staticProperty, // string(14) "staticProperty"
    new (trim(' MyClass '))()::staticMethod(),  // string(12) "staticMethod"
    new (trim(' MyClass '))()->property,        // string(8)  "property"
    new (trim(' MyClass '))()->method(),        // string(6)  "method"
    new (trim(' MyClass '))()(),                // string(8)  "__invoke"
);

This RFC preserves grammar for new expressions without constructor arguments' parentheses, because in some cases omitting parentheses around the new expression will result in a valid expression with different meaning:

(new MyClass)::$staticProperty; // Access static property on MyClass instance
new MyClass::$staticProperty;   // Instantiate class MyClass::$staticProperty
 
(new $myClass)::$staticProperty; // Access static property on an instance of class $myClass
new $myClass::$staticProperty;   // Instantiate class $myClass::$staticProperty
 
(new $myClass)->property; // Access non-static property on an instance of class $myClass
new $myClass->property;   // Instantiate class $myClass->property
 
(new $myClass)->method(); // Call method on an instance of class $myClass
new $myClass->method();   // Instantiate class $myClass->method

This RFC allows to omit parentheses around any new anonymous class expressions regardless of constructor arguments' parentheses:

var_dump(
    // string(8) "constant"
    new class { const CONSTANT = 'constant'; }::CONSTANT,
    // string(14) "staticProperty"
    new class { public static $staticProperty = 'staticProperty'; }::$staticProperty,
    // string(12) "staticMethod"
    new class { public static function staticMethod() { return 'staticMethod'; } }::staticMethod(),
    // string(8) "property"
    new class { public $property = 'property'; }->property,
    // string(6) "method"
    new class { public function method() { return 'method'; } }->method(),
    // string(8) "__invoke"
    new class { public function __invoke() { return '__invoke'; } }(),
);

Backward Incompatible Changes

None. Any code that is valid before the change will be valid after and interpreted in the same way.

Proposed PHP Version(s)

PHP 8.4

rfc/new_without_parentheses.1703889108.txt.gz · Last modified: 2023/12/29 22:31 by vudaltsov