最近 PHP の Slack コミュニティで聞き及んだ PHP 8.1 の奇妙な挙動をするコードの紹介です。具体的には次です。
<?php class A { private int $i = 1; // インスタンスが保持する変化する値 public function __toString(): string { // 文字列に変換されるたびに i の値が増えます。 return (string)$this->i++; } } // インスタンスをグローバル定数に割り当てます // これは PHP 8.1 から可能になった"引数デフォルト値にNew"の機能です // @see https://www.php.net/releases/8.1/ja.php#new_in_initializers // @see https://wiki.php.net/rfc/new_in_initializers const CONSTANT = new A; class B { // 新たな定数を文字列結合で構築します public const ClassConstant1 = '' . CONSTANT; public const ClassConstant2 = '' . CONSTANT; public const ClassConstant3 = '' . CONSTANT; public const ClassConstant4 = '' . CONSTANT; } // 定数を呼び出します // 呼び出したタイミングで定数が評価されます // これにより定義上で末尾ながらも最初に呼んだ ClassConstant4 が 1 を持ち、 // 最後に呼んだ ClassConstant1 が 4 を持ちます var_dump(B::ClassConstant4);// string(1) "1" var_dump(B::ClassConstant3);// string(1) "2" var_dump(B::ClassConstant2);// string(1) "3" var_dump(B::ClassConstant1);// string(1) "4" // 一度評価されるとその後は定まった値です var_dump(B::ClassConstant4);// string(1) "1" /* ↑の var_dump 群ではなく、次のコードを実行するとまた定数の値が変わります */ var_dump(B::ClassConstant1);// string(1) "1" var_dump(B::ClassConstant2);// string(1) "2" var_dump(B::ClassConstant3);// string(1) "3" var_dump(B::ClassConstant4);// string(1) "4"
Online PHP editor | output for eCOpP ↑のデモです
相当意図的に壊すコードですが、定数の挙動に不思議なものが増えました。こうなった直接の原因は PHP8.1 から増えた定数の初期化に new を使える機能です。
PHP: PHP 8.1.0 Release Announcement#引数デフォルト値にNew
この定数にインスタンスを割り当てられる機能自体はシングルトン的な扱いをするには便利そう(実際に定数に使ったことはありません)なのですが思わぬ挙動が生まれました。