PHP重鎮の廣川類氏によるコラム「PHPの最新状況:PHP 8.2採用の機能が決まる」(第25回)

PHP

廣川類

PHP 8.2の開発が進行中で、新機能や変更点が決定しています。PHP 8.1系でのシフトJISとUnicodeの間の文字コード変換問題は修正され、過去の動作に戻されました。また、PHP 8.2では乱数生成機能が強化され、統計的に偏りがなく、高速に生成可能であることが重要となります。PHP 7.1以来使用されてきたメルセンヌ・ツイスター法の代わりに、新しく工藤剛氏が主導するrandomエクステンションが導入され、乱数列の状態がインスタンスごとに保持可能になります。

 夏本番となり,暑い日々が続いています.次のマイナーバージョンであるPHP 8.2の開発が進行しており,採用される新しい機能および変更点が決定しています.本稿では,前回につづいてPHP 8.2の開発体制と提案されている機能の紹介を行います.

Mbstring関連の修正

現在の最新版であるPHP 8.1系において,シフトJISとUnicodeの間の文字コード変換が修正されました.PHPにおけるシフトJIS(文字コード:SJIS)は,マイクロソフト系OSにおける定義に基づいていましたが,PHP 8.1.0において一部の文字の変換がJIS規格(JIS X 0201)に基づく定義に変更されました.しかし,過去のコードとの互換性に関する問題を発生したため,7月7日にリリースされたPHP 8.1.8において,過去の動作に戻す修正が行われています.

以下のコードは,シフトJISの\をUnicodeに変換して,出力します.

echo mb_convert_encoding("\x5C","UCS2","SJIS");

PHP 8.1.0より以前のPHPではこのコードの出力は 0x005C (半角バックスラッシュ)でしたが,PHP 8.1.0から8.1.7までのバージョンでは,0x00A5(円記号)に変換されていました.日本語の符号化文字集合の規格であるJIS X 0201では,0x5Cは円記号となっています.一方,Unicodeではラテン1エンコーディングに基づき,0x5Cはバックスラッシュ,円記号は0xA5に割り付けられています.このため,PHP 8.1系における変更自体はJIS規格に基づく変更なのですが,マイクロソフト社のオペレーティングシステムであるWindowsでは,従来から,ディレクトリセパレータとして0x5Cを利用し,円記号として表示されているため,過去にシフトJISとUnicodeとの間の文字コードの相互変換の際に混乱を生じています.PHPのmbstringエクステンションでは,従来,現実的な妥協策として多くのユーザが利用するWindows方式を採用しています.

 同様に以下のようにチルダに関する変換も問題となります.

echo mb_convert_encoding("\x7E","UCS2","SJIS");

 PHP 8.1.0より以前のPHPではこのコードの出力は0x007E(チルダ)となります.一方,PHP 8.1.0から8.1.7までのバージョンで実行すると,0x203E(オーバーライン)が出力されます.この変換も先程と同様にJIS X 0201とWindowsにおけるシフトJISの扱いの差に起因して,Unicodeとの変換を行う際に互換性の問題を発生してきたコードとなります.

 これらの変換結果は,PHP 8.1.8以降のバージョンにおいて,互換性維持のためにいずれもPHP 8.1.0より前の動作に戻されています.

PHP 8.2の開発状況

 今年の11月24日に計画されている正式リリースに向けて,PHP 8.2の開発が進んでいます.6月から7月にかけてα版が3回リリースされ,7月19日にフィーチャーフリーズ(採用される機能の締め切り)が宣言されています.その後, 7月21日に最初のベータ版(β1)がリリースされており,今後,8月までに後2回のβリリースが行われる予定です.9月以降はリリース候補版が2週間に1回のペースでリリースされ,計画通りであれば11月24日にリリースを迎えることになります.前記のmbstringで発生した過去のバージョンの互換性の問題は見逃してしまった筆者にとっても反省事項ですが,読者の皆様もβ版,リリース候補版をテストしていただき,安定した正式版のリリースに向けてご支援いただければと思います.

乱数実装の改善

 PHP 8.2では,乱数生成機能が強化されます.乱数生成機能は,モンテカルロ法やゲームなどに利用され,統計的に偏りがなく,高速に生成可能であることが重要です.PHP 7.1以降では,デフォルトで乱数生成器にメルセンヌ・ツイスター(MT)法を使用しています.現在の実装には,乱数列の状態をグローバルに保持しており,MT法を内部的に使用するPHP組込み関数(例:str_shuffle)をコールしてしまうと,状態の再現性が失われてしまうという課題があります.また,MT法は乱数列を高速に生成できる優れた手法ですが,提案から25年が経過し,一部の統計的なテスト(CrushおよびBigCrush)をパスできないという問題が指摘されています.
 PHP 8.2では,工藤 剛氏が主導して提案するrandomエクステンションが導入されます.Randomエクステンションはデフォルトで有効となっており,乱数生成用クラスが導入され,乱数列の状態がインスタンス毎に保持できるようになります.また,疑似乱数列生成器として,メルセンヌ・ツイスター(MT19937)以外に,統計的に優れた特性を有するPCG64およびXoshiro256**を利用できるようになります.以下,課題を具体例で説明します.

 特定のシードに関する乱数列を生成したい場合,mt_srand関数により乱数列のシード(ここでは1234)を指定します.この後,mt_rand関数をコールすることで,指定した範囲(ここでは1から100)の乱数(整数)を生成することができます.

mt_srand(1234);
var_dump(mt_rand(1, 100));

 このコードを実行すると,整数値76が出力されます.
srand関数,rand関数は,それぞれ,mt_srand関数,mt_rand関数のエイリアスとして定義されていますので,以下のように実行しても出力は同じ(76)になります.

srand(1234);
var_dump(rand(1, 100));

次にmt_srand()関数のコール後,mt_rand()関数で乱数を取得するまでの間にstr_shuffle関数をコールする例を見てみましょう.

mt_srand(1234);
str_shuffle('abc');
var_dump(mt_rand(1, 100));

この例の出力は整数値7となり,同じシード値を指定しているにも関わらず,結果が異なってしまいます.これは,str_shuffleの内部処理でメルセンヌ・ツイスター法による乱数を使用しているためです.同様にshuffle関数,array_rand関数においてもメルセンヌ・ツイスター法による乱数を内部的に使用しているため,同様の事象が発生します.
シードを指定した直後に乱数を生成することで,このような問題を回避することは可能ですが,実際のアプリケーションでは乱数列を生成するコードが分散していることも多く,結果の再現性(予測性)がなくなることで,自動テストなどが困難となるといった問題が発生する懸念があります.

続いて,PHP 8.2で追加される乱数生成用クラスの使用方法を紹介します.この機能を利用する際には,まず,Random\Engine[手法]により疑似乱数生成エンジンのインスタンスを生成後,Random\Randomizerにより疑似乱数生成器のインスタンスを作成します.

$engine = new Random\Engine\Mt19937(1234);
$randomizer = new Random\Randomizer($engine);

ここでは,疑似乱数生成エンジンとしてMT19937とシード(1234)を指定しています.前記の例と同様に指定した範囲の乱数(整数)を生成する場合はgetInt()メソッドを使用します.

$randomizer->getInt(1,100)

 この場合,乱数列の状態はインスタンス毎に保持されるため,前記の例のように乱数生成前にstr_shuffle関数のようなMT乱数生成器を内部的に利用する関数をコールしても,影響を受けません.
 この機能では,この他にも表1に示すような乱数生成用メソッドが用意されており,従来の関数の代替として使用することができます.

 表1 従来の関数と新機能のメソッドの対応

従来の関数Random\Randomizerクラス
mt_rand(), random_int()getInt()
random_bytes()getBytes()
shuffle()shuffleArray()
str_shuffle()shuffleBytes()

その他の暗号生成エンジンとして,メルセンヌ・ツイスターよりも新しく,統計的な課題を解決しているPCG64 (pcg_onseq-128-xsl-rr-64)またはXoshiro256**を利用する場合はそれぞれ以下のようにインスタンスを生成します.

$engine = new Random\Engine\PcgOneseq128XslRr64([seed]);
$engine = new Random\Engine\Xoshiro256StarStar([seed]);

 暗号論的にセキュアな疑似乱数生成器(CSPRNG)を利用する場合,以下のようにエンジンとしてSecureクラスのインスタンスを生成します.この場合,シードを指定することはできません.試験時はシードが指定可能な他のエンジンを利用し,運用時にはSecureクラスを利用することも考えられます.

$engine = new Random\Engine\Secure();

 互換性のため,従来の乱数生成関数も引き続き利用可能ですが,今後,今回紹介した新しい機能が普及すると思われます.

 今回は,mbstringのシフトJISに関する修正と,PHP 8.2の新機能として乱数生成機能の改善を紹介しました.次回以降もPHP 8.2の新機能などについて紹介していきたいと思います.

<< PHP重鎮 廣川類氏のコラム第24回「PHPの最新状況:PHP 8.2開発本格スタート」PHP重鎮の廣川氏によるコラム「 PHPの最新状況:PHP 8.2β版リリースされる」(第26回) >>

関連記事

Webサイト運用の課題解決事例100選 プレゼント

Webサイト運用の課題を弊社プロダクトで解決したお客様にインタビュー取材を行い、100の事例を108ページに及ぶ事例集としてまとめました。

・100事例のWebサイト運用の課題と解決手法、解決後の直接、間接的効果がわかる

・情報通信、 IT、金融、メディア、官公庁、学校などの業種ごとに事例を確認できる

・特集では1社の事例を3ページに渡り背景からシステム構成まで詳解