関数の機能強化
続けて、新しい関数や既存の関数の動作改善について紹介します。
str_pad関数のマルチバイト版mb_str_pad関数
PHP 8.3では、str_pad関数をマルチバイト対応にしたmb_str_pad関数が利用可能になりました。str_pad関数は、文字列の左側あるいは右側を、指定した長さまで、指定した文字列で埋めて返します。
$s = str_pad(対象文字列, 結果の文字数, 埋める文字列);
$str = 'Hello'; echo str_pad($str, 10, '!') . PHP_EOL; // 実行結果:Hello!!!!! $str = 'こんにちは'; echo str_pad($str, 20, '!') . PHP_EOL; // 実行結果:こんにちは!〓
対象の文字列が日本語などマルチバイト文字を含む場合、期待通りに動作せずに結果の文字列が壊れています。この問題を解決するために、マルチバイト対応版str_pad関数であるmb_str_pad関数が使えるようになりました。書式は同じです。
$str = 'Hello'; echo mb_str_pad($str, 10, '!') . PHP_EOL; // 実行結果:Hello!!!!! $str = 'こんにちは'; echo mb_str_pad($str, 20, '!') . PHP_EOL; // 実行結果:こんにちは!!!!!!!!!!!!!!! $str = 'こんにちはPHP'; echo mb_str_pad($str, 20, '!') . PHP_EOL; // 実行結果:こんにちはPHP!!!!!!!!!!!!
対象の文字列がシングルバイトであればstr_pad関数と同様に動作します。マルチバイトであっても、指定した文字数になるまで埋められます。シングルバイト文字とマルチバイト文字が混在しても大丈夫です。
ランダマイザ(Randomizer)の機能強化
PHP 8.3では、ランダマイザ(Randomizer)の機能が強化され、getBytesFromStringメソッドによるランダム文字列生成、getFloatメソッドとnextFloatメソッドによる浮動小数点数の乱数生成が可能になりました。getFloatメソッドでは、範囲の上限と下限を含むかといった開区間/閉区間の指定も可能になっています。
$randomizer->getBytesFromString(選択対象文字列, 文字数); $randomizer->getFloat(下限値, 上限値, 開区間/閉区間の指定); $randomizer->nextFloat();
$randomizer = new \Random\Randomizer();
$str = implode('-', str_split($randomizer->getBytesFromString('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', 16), 4));
(1)
echo $str . PHP_EOL;
$str = $randomizer->getBytesFromString('xyyzzz', 18); (2)
echo $str . PHP_EOL;
$value = $randomizer->getFloat(-100, 100, \Random\IntervalBoundary::OpenClosed); (3)
echo $value . PHP_EOL;
$value = $randomizer->nextFloat(); (4)
echo $value . PHP_EOL;
// 実行結果:QBZU-2KPI-S8M2-D3EA
// yxyyxxzyzxxyyyzzzz
// -51.837229038424
// 0.54015664001528
getBytesFromStringメソッドは、引数で指定される選択対象文字列内の文字を使い、指定される文字数のランダム文字列を生成します。(1)では各文字の発生確率は同等ですが、(2)のように文字を重複させると発生確率をそれに応じて増加させることができます。
getFloatメソッドは、引数で指定される範囲の浮動小数点数の乱数を生成します。第3引数は、指定する範囲の上限と下限を含めるかを以下のいずれかで指定します。
- IntervalBoundary::ClosedOpen:下限は含む、上限は含まない(既定値)
- IntervalBoundary::ClosedClosed:下限と上限を含む
- IntervalBoundary::OpenClosed:下限は含まない、上限は含む
- IntervalBoundary::OpenOpen:下限も上限も含まない
nextFloatメソッドは、PHP 8.2でサポートされたnextIntメソッドの浮動小数点版であり、0から1.0未満の乱数を生成して返します。つまり、以下と等価です。
$value = $randomizer->getFloat(0, 1.0, \Random\IntervalBoundary::ClosedOpen);
JSON文字列を検証するjson_validate関数の追加
PHP 8.3では、あるJSON文字列のフォーマットが正しいものかどうかを検証する、json_validate関数が使えるようになりました。従来は、JSONのデコーダーであるjson_decode関数に渡してみてはじめて正当性が判明したのですが、それを事前に検証できるわけです。json_decode関数ではデコード後のJSONオブジェクトを返すので、それを利用しない場合には無駄な処理となっていました。
$json = '{ "a": 1, "b": 2 }'; // 正しいJSON文字列
var_dump(json_validate($json)); // bool(true)
var_dump(json_decode($json)); // object(stdClass)#1 (2) {…
$json = '{ "a": "b": 2 }'; // 形式がおかしいJSON?文字列
var_dump(json_validate($json)); // bool(false)
var_dump(json_decode($json)); // NULL
このように、json_validate関数でチェックしてからjson_decode関数に渡すことができますが、デコード後のオブジェクトを使うなら最初からjson_decode関数に渡してもよいでしょう。
range関数の動作改善
PHP 8.3では、range関数の動作が修正されました。range関数は、指定される範囲と間隔で決まる複数の値による配列を生成して返します。PHP 8.2までは、範囲の指定が矛盾(開始値と終了値が逆、間隔の正負が範囲に合わないなど)していても特にエラーにはならず、結果が予測できない動作になっていました。PHP 8.3では、範囲と間隔の矛盾は指摘され、開始値や終了値に空文字列が与えられたときにも指摘されます。
$r = range(開始値, 終了値, 間隔);
$r = range(1, 5, 2);
echo implode(',', $r) . PHP_EOL; (1) 実行結果:1,3,5
$r = range("A", "D");
echo implode(',', $r) . PHP_EOL; (2) 実行結果:A,B,C,D
$r = range("", "D"); (3)
// Warning: range(): Argument #1 ($start) must not be empty, casted to 0
// Warning: range(): Argument #1 ($start) must be a single byte string if argument #2 ($end) is a single byte string, argument #2 ($end) converted to 0
$r = range(1, 5, -1); (4)
// ValueError: range(): Argument #3 ($step) must be greater than 0 for increasing ranges
(1)(2)はrange関数の正しい利用法であり、結果は期待通りとなります。(3)では始点が空文字列であるので、警告とともに0にキャストされます。このとき終点は文字列であるので、こちらも警告とともに0にキャストされます。結果として0のみからなる配列となります。(4)は指定範囲が増加方向であるのに対して間隔が負であるので、矛盾するとしてValueErrorとなります。
unserialize関数の動作改善とエラーハンドリング強化
PHP 8.3では、unserialize関数の引数末尾に不要な文字列がある場合の扱いが変更されます。unserialize関数は、serialize関数によってPHP独自の形式にシリアライズされた文字列をアンシリアライズ(デシリアライズ)します。以下はシリアライズされた整数型の値10をアンシリアライズする例です。
$i = unserialize('i:10;'); // 実行結果:10
unserialize関数は、単一の値、配列、オブジェクトに対して動作するので、例えば以下のような場合には「s:5:"Hello";」は余計な文字列となります。PHP 8.2以前では、このような引数が与えられた場合でも警告やエラーとならずに問題なくアンシリアライズされます。
$i = unserialize('i:10;s:5:"Hello";'); // 実行結果:10
目的は達成されるので問題ないように見えますが、このようなデータが渡されることは、シリアライズ文字列の生成ロジックに何らかの問題がある可能性があるということです。場合によってはセキュリティ上の脆弱(ぜいじゃく)性にもつながる可能性があるとして、PHP 8.3では同様のケースで警告を発生するようになりました。
$i = unserialize('i:10;s:5:"Hello";'); // 実行結果:10
// Warning: unserialize(): Extra data starting at offset 5 of 17 bytes
開発者は、この警告が発生したらunserialize関数に渡す文字列の生成ロジックを精査し、意図しない文字列を渡すことになっていないか検証することができます。
この他、アンシリアライズ時に問題が発生した際のエラー処理がUnserializationFailedExceptionによって一元化されることが決まりました。ただし導入はPHP 9.0からで、PHP 8.3ではE_NOTICEの発生をE_WARNINGに置き換えるなどの準備段階としての変更のみが行われています。PHP 8.2までは、問題の種類によってE_NOTICE、E_WARNING、UnexpectedValueExceptionなどが別に発生して、一括のハンドリングが困難でした。
$i = unserialize('bad');
// Notice: unserialize(): Error at offset 0 of 3 bytes
$i = unserialize('i:12345678901234567890;');
// Warning: unserialize(): Numerical result out of range
$i = unserialize('O:11:"ArrayObject":4:{i:0;i:0;i:1;a:3:{s:3:"one";i:1;s:3:"two";i:2;s:5:"three";i:3;}i:6;a:0:{}i:3;N;}');
// Fatal error: Uncaught UnexpectedValueException: Incomplete or ill-typed serialization data
PHP 8.3では、以下のようにE_WARNINGが発生するように改められました。UnexpectedValueExceptionの発生については変更ありません。
$i = unserialize('bad');
// Warning: unserialize(): Error at offset 0 of 3 bytes
$i = unserialize('i:12345678901234567890;');
// Warning: unserialize(): Numerical result out of range
array_sum/array_product関数の動作改善
PHP 8.3では、GMP数が与えられたときのarray_sum関数とarray_product関数の動作が改善されました。GMPとはGNU MP(Multiple Precision)と呼ばれるGNUの多倍長整数演算ライブラリのことで、GMP数はそれが取り扱う数のことです。多倍長精度の符号付き整数だけではなく、有理数や浮動小数点数も取り扱うことができます。従来のarray_sum関数とarray_product関数では、引数にGMP数が与えられたときに期待通りの演算結果を得られないという問題がありました。
$sum = gmp_init(100) + 200; // (1)実行結果:300 $pro = gmp_init(100) * 200; // (2)実行結果:20000 echo $sum, ", ", $pro, PHP_EOL; $sum = array_sum([gmp_init(100), 200]); // (3)実行結果:300(PHP 8.2では200) $pro = array_product([gmp_init(100), 200]); // (4)実行結果:20000(PHP 8.2では200) echo $sum, ", ", $pro, PHP_EOL;
(1)(2)はPHPの算術演算子を使っていますが、結果は正しく得られています。これに対して(3)(4)は、PHP 8.2ではGMP数は無視されて、正しい結果は得られません。PHP 8.3では、これらの問題を解決することで、算術演算子と同様の処理結果が得られるようになっています。
まとめ
今回は、PHP 8.3の新機能のうち、言語仕様と関数における機能強化について紹介しました。
PHPでは、このように非常に細かな改変が各バージョンで施されています。よりモダンで使い勝手のよい言語として成長し続けるPHPを、今後も見守っていきたいものです。
