PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0
2022/12/08にリリースされました。
2022/07/19、PHP8.2がフィーチャーフリーズしました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2022/11/24にPHP8.2.0がリリースされる予定です。
というわけでPHP8.2で実装されるRFCを見てみましょう。
RFC
Disjunctive Normal Form Types
賛成25、反対1で受理。
選言標準形です。
UNION型と交差型を同時に使えるようになります。
思う存分型パズルで遊べますね。
// A型、もしくはB型かつC型、もしくはint
function hoge( A | (B & C) | int $param){}
Allow null and false as stand-alone types
賛成38、反対0の全会一致で受理。
null型とfalse型です。
null型にはnullしか入れることができず、false型にはfalseしか入れることができません。
class Nil {
public null $nil = null;
public function foo(false $falsy): false{
return false;
}
}
元々UNION型を導入する際にnull疑似型とfalse疑似型が導入されました。
これはUNION型の一部としてのみ記述可能で、単独では使えないという特殊な型でした。
当時としてはそうする合理的理由があったわけですが、その後交差型などで型システムが拡張された結果、単独でも使えた方がいいだろうということになり疑似型から通常の型に昇格しました。
Add true type
賛成33、反対0の全会一致で受理。
true型です。
true型にはtrueしか入れることができません。
function foo(true $true): true{
return true;
}
false型が導入されたので、そうなるとtrue型が無いのは片手落ちになってしまいます。
また静的解析ツールにとっても便利なこと、実際にtrueだけを返す関数が存在するなど、他にも利点があるため導入が決定しました。
Readonly classes
賛成28、反対7で受理。
readonlyクラスです。
readonly class Test {
public int $foo;
}
$t = new Test();
$t->foo = 1; // OK
$t->foo = 2; // NG 上書きは不可
$t->bar = 3; // NG 動的プロパティは禁止
readonlyクラスは、静的プロパティは設定できず、動的プロパティも使用できず、型指定は必須であり、一度設定したプロパティは書き替えることができません。
できることが非常に限定されるかわりに、堅固な運用が可能になります。
Random Extension 5.x
賛成21、反対0の全会一致で受理。
PHPの乱数は、これまで様々な問題がありました。
有名どころではメルセンヌツイスタの実装が間違ってるとか範囲によっては奇数しか出てこないとか。
このあたりはある程度個別に解消されたのですが、もっと深刻な問題として状態がグローバルというものがあります。
これのせいで外部ライブラリで乱数が乱れたり、状況再現ができないみたいな問題が発生するみたいです。
正直そんなコード書いたことないからよくわかりませんが。
他にもRFCには様々な問題点が書かれていました。
ということで、そのあたりを解決するRandom\Randomizer
クラスを導入します。
$engine = new Random\Engine\Secure();
$randomizer = new Random\Randomizer($engine);
$randomizer->getInt(1, 10); // mt_rand
$randomizer->getBytes(10); // random_bytes
$randomizer->shuffleArray([1, 2, 3]); // shuffle
$randomizer->shuffleString('abc'); // str_shuffle
Random Extension Improvement
Random Extension
の補足RFCです。
投票開始後に発覚した問題点の改善や、不足していた機能の追加などです。
$randomizer->pickArrayKeys([1, 2, 3], 1); // array_rand
$randomizer->shuffleBytes('abc'); // shuffleStringから改名
shuffleString
をマルチバイト文字に使うと壊れるので、適切なshuffleBytes
に改名する、array_randを実装する、当初のRFCに含まれていたRandom\Engine\CombinedLCG
は乱数として低品質であるため削除する、等が含まれています。
Redacting parameters in back traces
賛成24、反対1で受理。
引数をスタックトレースに出力しないようにするアトリビュート#[\SensitiveParameter]
です。
function test(
$foo = null,
#[\SensitiveParameter]
$bar = null,
$baz = null
) {
throw new \Exception('Error');
}
test(
bar: 'bar',
baz: 'baz',
);
/* エラーメッセージは以下のようになる
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(13): test(NULL, Object(SensitiveParameterValue), 'baz')
#1 {main}
thrown in test.php on line 8
*/
アトリビュート#[\SensitiveParameter]
を設定した引数$bar
の値は、例外スタックトレースやvar_dump等に出力されなくなります。
どうしてこんなものが必要かというと、たとえばPDO::__construct()は失敗すると例外を吐くのですが、display_errorsがオンだと思いっきりDBパスワードが表示されます。
これがうっかり設定ミスとかのせいで本番環境で発生したら大惨事です。
またクレカ番号等のセンシティブな情報がアクセスログに流れてそこから流出という可能性もありえます。
そういうミスがあった場合でも大丈夫なように、という安全策ですね。
Deprecate dynamic properties
賛成52、反対25の僅差で受理。
動的プロパティ禁止です。
class Test {}
$t = new Test();
$t->foo = 1; // PHP <= 8.1: $t->fooが生える
// PHP 8.2: E_DEPRECATE
// PHP 9.0: Exception
未定義のプロパティは、PHP8.2ではE_DEPRECATEを出すようになり、そしてPHP9以降は例外になります。
私は今までたくさんのPHP RFCを見てきましたが、このRFCが、これまでのPHPの思想から最も異質なものであると感じました。
#[AllowDynamicProperties]
class Test {}
$t = new Test();
$t->foo = 1; // OK
これまでの動作を行わせたい場合、アトリビュート#[AllowDynamicProperties]
を指定することで互換します。
Deprecate partially supported callables
賛成32、反対0の全会一致で受理。
$callable = 'self::hoge';
is_callable($callable); // OK
call_user_func($callable); // OK
$callable(); // NG ←
これまで、is_callableやcall_user_funcではtrueが返ってくるのに実際はコールバックできない、みたいな矛盾した構文が幾つか存在しました。
よって、これらの動作を揃えるようにします。
PHP8.2でE_DEPRECATEとなり、そしてPHP9で削除されます。
Expand deprecation notice scope for partially supported callables
Deprecate partially supported callables
のフォローアップです。
該当のRFCでは『is_callable()やcallableは、サポートされなくなるまで非推奨の警告は発生しません』と、エラーを出さないことを明示していましたが、これだと危ない挙動が存在することが判明しました。
class Foo {
public function bar() {
if (is_callable('static::methodName')) {
static::methodName();
}
}
}
(new Foo())->bar();
このコード、PHP8.xでは何のエラーも出ないのに、PHP9.0で突然動作が変わることになります。
ということで、対応しなくなる書式をis_callable
等に渡した場合にもE_DEPRECATEDが発生するようにします。
Locale-independent case conversion
賛成29、反対1で受理。
strtolowerなどの関数は、なんとロケールの影響を受けます。
マニュアルには『たとえばデフォルトの「C」ロケールである場合は、 A ウムラウト (Ä) のような文字は変換されません。』とありますが、そもそもロケールを変えたら変更できる、という挙動のほうがわかりにくくて危険です。
そのため、これらの関数はロケールの影響を受けないようにします。
具体的にはC
ロケールであるものとして扱います。
Deprecate ${} string interpolation
賛成31、反対1で受理。
文字列内変数展開の文法を一部廃止します。
$foo = 'value';
$bar = 'foo';
var_dump(
"$foo", // value
"{$foo}", // value
"${foo}", // value
"${$bar}" // value
);
この4種類の書式のうち、後者2つを廃止します。
PHP8.2でE_DEPRECATED、そしてPHP9.0でエラーになります。
2番目と3番目は同じに見えますが、実は2番目のほうでは使えるのに3番目では駄目という書式が存在します。
var_dump("{$foo->bar()}"); // OK
var_dump("{$foo['bar']->baz()}"); // OK
var_dump("${foo->bar()}"); // NG!
var_dump("${foo['bar']->baz()}"); // NG!
たいへんわかりにくいので片方しか使えないようにします。
また4番目の可変変数は3番目と書式が被るうえ、全く使われていないので削除します。
${$bar}
は今後{${$bar}}
と書くことになります。
まあ、文字列内可変変数なんてややこしいことするんじゃねえと言いたいですが。
RFCには色々と細かい例が書かれていて頭が混乱します。
Deprecate and remove utf8_decode() and utf8_encode()
賛成33、反対2で受理。
utf8_decodeとutf8_encodeを削除します。
この関数は、任意の文字列をUTF-8
にエンコードデコードできるかと思いきや、変換相手はLatin 1
固定です。
非常に誤解を招く関数名であり、容易に間違った実装をしてしまいがちです。
従ってこの関数を削除し、代替として間違えた理解をしづらいmb_convert_encodingを推奨します。
MySQLi Execute Query
賛成24、反対0で受理。
MySQLiに新しい関数mysqli_execute_queryを追加する提案。
// これまで
$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->bind_param('sii', $name, $type1, $type2);
$statement->execute();
foreach ($statement->get_result() as $row) {
print_r($row);
}
// 今後
foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
print_r($row);
}
MySQLiでSELECTするには、mysqli_prepare()
してmysqli_execute()
してmysqli_stmt_get_result()
するという面倒な手順が必要です。
これらを単純にまとめただけなのですが、たったこれだけでも使い方がぐっと楽になりますね。
これ同様の機能はPDOにもないので、そっちにも欲しいですね。
Remove support for libmysql from mysqli
賛成30、反対0の全会一致で受理。
MySQLのモジュールはmysqlndとlibmysqlの2種類が存在します。
このうちlibmysqlを削除しようという提案。
mysqlndはPHP5.4以降ずっとデフォルトであり、PHP同梱のため別途インストールする必要がなく、パフォーマンス上有利であり、libmysqlには存在しない機能もあったりと、基本的にlibmysqlを選ぶ利点はほぼありません。
ということでlibmysqlを削除し、mysqlndに一本化します。
Make the iterator_*() family accept all iterables
iterator_to_arrayとiterator_countにiterableを渡せるようにするRFC。
iterator_to_array
は名前に反してTraversableしか受け付けません。
iterator_to_array([1, 2]); // Fatal error: Uncaught TypeError
とてもわかりにくいので、foreach同様iterableを受け付けられるようにします。
なおiterable = array|Traversable
なので、実質的には配列を渡せるようになるだけです。
Fetch properties of enums in const expressions
賛成24、反対11の僅差で受理。
定数式の中でENUMの->
を使えるようにします。
enum E: string {
case Foo = 'foo';
}
const C = E::Foo->name; // 8.2からOK
Nikitaがこれサポートしていいんじゃないと言ったことで方向性が決まったみたいです。
いまだに多大な影響力を持ってますねNikita。
当初はENUMだけでなく全てのオブジェクトに対応するようにしたかったみたいですが、こちらについてはパフォーマンス等の理由で中止されました。
class X {
public $name = 'value';
}
const C = (new X())->name; // こっちはPHP8.2以降も駄目
Constants in Traits
賛成28、反対12で受理。
トレイトに定数を書けるようになります。
trait T {
public const CONSTANT = 42;
}
class C1 {
use T;
public function doSomething(): void {
// OK
echo self::CONSTANT;
echo static::CONSTANT;
echo $this::CONSTANT;
// Fatal Error
echo T::CONSTANT;
}
}
具象化したクラスにおいて宣言されたのと同じように、トレイト定数を扱うことができます。
T::CONSTANT
のように、トレイトを直接参照することはできません。
また、as
演算子によるエイリアスやinsteadof
による衝突回避は禁止され、定数が被るとエラーになります。
そもそもトレイトが実装されたときにどのような意図で定数が対応されなかったのかというと、実装者によると特に考えてなかったそうです。
感想
PHP8.1に引き続き、型に関連するRFCが多いですね。
PHP7の後半あたりからどんどん型宣言でできることが増え、そしてついに今回の選言標準形において、あらゆる型を表現できるまでになってしまいました。
ここまでできる言語はそう多くないのではないでしょうか。
そして新機能以外では、よくわからなかったり曖昧だったりする機能の削除が目立ちます。
さてRFCの総量自体は、PHP8.1やPHP8.0に比べると格段に減ってしまいました。
これはやはりNikitaの離脱という事件の影響が大きいと思われます。
実際この後しばらくRFCの提出や投票が滞ったりしましたからね。
しかしその後Nikitaの後を継ぐ者たちが現れ、締め切り間際には彼らによって作られたRFCも幾つも投票にかけられるまでになりました。
まあ概ね却下されてしまいましたが。
今後は彼らも成長し、再びPHPの進化の速度は上がっていくに違いありません。
PHP8.2は、既にα版が提供されており、Windows版もしっかりあります。
今回紹介した機能の一部はまだ搭載されてなかったりしますが、これらの新機能を早々と試してみたい人はダウンロードしてみてはいかがでしょうか。