【PHP8.4】`new HOGE()->run();`って書けるようになる。

これまで[クラスを即時実行するサンプル](https://qiita.com/rana_kualu/items/38df213f5e30326a81a8)を書くとき、newをかならず`()`で括っていました。

```php
(new HOGE())->run();
```

これは別に伊達や酔狂ではなく、こうしないとエラーになるからです。
`new HOGE()->run();`は、`(new HOGE())->run();`ではなく`new (HOGE()->run());`と解釈されます。

しかしまあ、どう考えても不便でしかないので、`(new HOGE())->run();`って解釈しようぜという提案がされました。
既に受理されており、PHP8.4から括弧を付けずに書くことができるようになります。

以下は該当のRFC、[new MyClass()->method() without parentheses](https://wiki.php.net/rfc/new_without_parentheses)の日本語訳です。

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

## Introduction

インスタンス化から即時のメンバーアクセス機能は[PHP5.4で導入されました](https://php-legacy-docs.zend.com/manual/php5/en/migration54.new-features)。
これによって中間変数を使わずクラス定数、メソッド、プロパティにアクセスできるようになりました。
が、この構文はnewを括弧で囲まないといけません。


```php
class Request implements Psr\Http\Message\RequestInterface
{
    // ...
}
 
// OK
$request = (new Request())->withMethod('GET')->withUri('/hello-world');
 
// PHP Parse error: syntax error, unexpected token "->"
$request = new Request()->withMethod('GET')->withUri('/hello-world');
```

このRFCでは、後者の構文を有効にします。
ユーザから多くの希望が上がっている変更であり ( [externals#66197](https://externals.io/message/66197)・[bugs.php#70549](https://bugs.php.net/bug.php?id=70549)・[externals#101811](https://externals.io/message/101811)・[externals#113953](https://externals.io/message/113953) )、[50万行以上のコードを簡素化](https://github.com/search?q=path%3A*.php+language%3APHP+%2F%5C%28%5Cs*new%5Cs%2B%5B%5E%5C%28%5D%2B%5Cs*%5C%28%5B%5E%5C%29%5D*%5C%29%5Cs*%5C%29%5Cs*-%3E%2F&type=code&ref=advsearch)することができます。
また、括弧を必要としない他の言語 ( [Java](https://onecompiler.com/java/3zxuee3bm)・[C#](https://onecompiler.com/csharp/3zxudhw4s)・[TypeScript](https://onecompiler.com/typescript/3zxudfpyy) ) と文法を揃えます。


## Proposal

コンストラクタ引数のある構文について、newを囲むかっこを省略できるようになります。

```php
class MyClass extends ArrayObject
{
    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'; }
}
```

上記クラスについて、以下のように書けるようになります。

```php
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"
    new MyClass(['value'])[0],      // string(5)  "value"
);
 
$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"
    new $myClass(['value'])[0],      // string(5)  "value"
);
 
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"
    new (trim(' MyClass '))(['value'])[0],      // string(5)  "value"
);
```

括弧を付けない構文では、いままでと挙動に変更はありません。

```php
new MyClass::CONSTANT;         // シンタックスエラーのまま
new $myClass::CONSTANT;        // シンタックスエラーのまま
new MyClass::$staticProperty;  // new (MyClass::$staticProperty)と同じ
new $myClass::$staticProperty; // new ($myClass::$staticProperty)と同じ
new $myObject->property;       // new ($myObject->property)と同じ
new MyArrayConst['class'];     // シンタックスエラーのまま
new $myArray['class'];         // new ($myArray['class'])と同じ
```

コンストラクタ引数の有無にかかわらず、無名クラスのnewを囲むかっこを省略できるようになります。

```php
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'; } }(),
    // string(5) "value"
    new class (['value']) extends ArrayObject {}[0],
);
```


## Why the proposed syntax is unambiguous

この構文が曖昧にならない理由。

一見すると`new MyClass()->method()`は曖昧に見えるかもしれません。
しかしそれは、実は`new MyClass()`にも言えることです。
これは`new (MyClass())`ですか？それとも`new MyClass`ですか？

`new MyClass()`は`MyClass()`関数呼び出しの結果をインスタンス化するのではなく、引数が0のMyClassクラスのインスタンス化であると解釈されます。
これは、PHPはnew直後の式をクラス名と解釈するためです。

それでは`MyClass::new()->method()`も考えてみましょう。
これは`MyClass::(new()->method())`でも`MyClass::(new())->method()`でもなく、`(MyClass::new())->method()`となります。
`MyClass::new()`が最初に評価されると考えるのが自然というわけです。

従って、`new MyClass()->method()`も最初に`new MyClass()`が評価されるべきでしょう。

[文法レベル](https://github.com/php/php-src/blob/948b2bc2110667cfe53cec253893a3b021f22449/Zend/zend_language_parser.y#L1126)では以下のようになります。

```
new MyClass();
^   ^      ^
|   |      |
|—T_NEW    |—ctor_arguments
    |
    |—class_name
 
 
new $class();
^   ^     ^
|   |     |
|—T_NEW   |—ctor_arguments
    |
    |—new_variable (cannot have calls!)
 
new (trim(' MyClass '))();
^   ^                  ^
|   |                  |
|—T_NEW                |—ctor_arguments
    |
    |—(expr)
```

パーサはT_NEWに出会うと、次に来るものを式ではなくクラス名とみなします。
これによって、引数の括弧を含む式を誤解なく解釈することができます。
逆に引数の括弧がない構文を解釈できない理由もわかります。
括弧はクラス名の終わりおよび次の式の始まりを表します。

## Other syntax ideas

その他の構文のアイデア。
本RFCには含まれていません。

### Allow to omit the new keyword

`new`キーワードの省略。

Kotlinなど一部の言語では、[MyClass()式からインスタンス化](https://onecompiler.com/kotlin/42b54ds4u)ができます。

Kotlinでは[クラスと関数が同じ名前になれない](https://onecompiler.com/kotlin/42b579yhu)ので混同はありませんが、PHPでは[クラスと関数が同じ名前になれる](https://3v4l.org/mfUVp)ので、newキーワードを省略することは現時点では不可能です。


### MyClass::new(), MyClass::create()

専用の静的コンストラクタの導入。

`MyClass::new()`・`MyClass::create()`のような専用の静的コンストラクタを導入すると、既存のオブジェクトの互換性が失われる可能性があります。


## Backward Incompatible Changes

互換性のない変更はありません。

## Proposed PHP Version(s)

PHP8.4

## Proposed Voting Choices

投票期間は2024/05/09～2024/05/24、投票の2/3の賛成で受理されます。

本RFCは賛成25反対4の賛成多数で可決されました。


## Implementation

https://github.com/php/php-src/pull/13029

# 感想

何も考えずに書くと`new HOGE()->run()`まで進んでからあっこれじゃ駄目だったと戻って`()`を入れ込む羽目になっていたので、これは助かりますね。
というかむしろ、どうして今までできなかったんだよと言いたいところでありますが。

この変更によって既存のコードが動かなくなることは一切無いみたいなので、純粋に大変助かる追加ですね。


ところでPHPでは引数のないクラスのインスタンス化は`()`を省略して`$c = new MyClass;`と書くことができます。
RFCの例文ではその先がメソッド`new MyClass->method();`である場合が何故か示されていませんが、プルリクでは[この場合もシンタックスエラーになる](https://github.com/php/php-src/pull/13029#issuecomment-1869744480)とのことでした。
同じ書き方なのに片方は動いてもう片方は動かないというのはなんか気持ち悪さを感じますね。

まあ、私は括弧省略構文を一切使わないから関係ないんですけどね。
そもそも括弧省略構文なんてものが存在するのが悪い。