これまでクラスを即時実行するサンプルを書くとき、newをかならず()
で括っていました。
(new HOGE())->run();
これは別に伊達や酔狂ではなく、こうしないとエラーになるからです。
new HOGE()->run();
は、(new HOGE())->run();
ではなくnew (HOGE()->run());
と解釈されます。
しかしまあ、どう考えても不便でしかないので、(new HOGE())->run();
って解釈しようぜという提案がされました。
既に受理されており、PHP8.4から括弧を付けずに書くことができるようになります。
以下は該当のRFC、new MyClass()->method() without parenthesesの日本語訳です。
PHP RFC: new MyClass()->method() without parentheses
Introduction
インスタンス化から即時のメンバーアクセス機能はPHP5.4で導入されました。
これによって中間変数を使わずクラス定数、メソッド、プロパティにアクセスできるようになりました。
が、この構文はnewを括弧で囲まないといけません。
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・bugs.php#70549・externals#101811・externals#113953 )、50万行以上のコードを簡素化することができます。
また、括弧を必要としない他の言語 ( Java・C#・TypeScript ) と文法を揃えます。
Proposal
コンストラクタ引数のある構文について、newを囲むかっこを省略できるようになります。
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'; }
}
上記クラスについて、以下のように書けるようになります。
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"
);
括弧を付けない構文では、いままでと挙動に変更はありません。
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を囲むかっこを省略できるようになります。
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()
が評価されるべきでしょう。
文法レベルでは以下のようになります。
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()式からインスタンス化ができます。
Kotlinではクラスと関数が同じ名前になれないので混同はありませんが、PHPではクラスと関数が同じ名前になれるので、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
感想
何も考えずに書くとnew HOGE()->run()
まで進んでからあっこれじゃ駄目だったと戻って()
を入れ込む羽目になっていたので、これは助かりますね。
というかむしろ、どうして今までできなかったんだよと言いたいところでありますが。
この変更によって既存のコードが動かなくなることは一切無いみたいなので、純粋に大変助かる追加ですね。
ところでPHPでは引数のないクラスのインスタンス化は()
を省略して$c = new MyClass;
と書くことができます。
RFCの例文ではその先がメソッドnew MyClass->method();
である場合が何故か示されていませんが、プルリクではこの場合もシンタックスエラーになるとのことでした。
同じ書き方なのに片方は動いてもう片方は動かないというのはなんか気持ち悪さを感じますね。
まあ、私は括弧省略構文を一切使わないから関係ないんですけどね。
そもそも括弧省略構文なんてものが存在するのが悪い。