SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ますます便利になるPHPの新機能を探ろう!

より使いやすくなったPHP 8.3の新機能──言語仕様や関数の強化ポイントは?

ますます便利になるPHPの新機能を探ろう! 第4回

関数の機能強化

 続けて、新しい関数や既存の関数の動作改善について紹介します。

str_pad関数のマルチバイト版mb_str_pad関数

 PHP 8.3では、str_pad関数をマルチバイト対応にしたmb_str_pad関数が利用可能になりました。str_pad関数は、文字列の左側あるいは右側を、指定した長さまで、指定した文字列で埋めて返します。

$s = str_pad(対象文字列, 結果の文字数, 埋める文字列);
リスト mb_str_pad.php
$str = 'Hello';
echo str_pad($str, 10, '!') . PHP_EOL;	// 実行結果:Hello!!!!!
$str = 'こんにちは';
echo str_pad($str, 20, '!') . PHP_EOL;	// 実行結果:こんにちは!〓

 対象の文字列が日本語などマルチバイト文字を含む場合、期待通りに動作せずに結果の文字列が壊れています。この問題を解決するために、マルチバイト対応版str_pad関数であるmb_str_pad関数が使えるようになりました。書式は同じです。

リスト mb_str_pad.php
$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.php
$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_validate.php
$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(開始値, 終了値, 間隔);
リスト range.php
$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をアンシリアライズする例です。

リスト unserialize.php
$i = unserialize('i:10;');	// 実行結果:10

 unserialize関数は、単一の値、配列、オブジェクトに対して動作するので、例えば以下のような場合には「s:5:"Hello";」は余計な文字列となります。PHP 8.2以前では、このような引数が与えられた場合でも警告やエラーとならずに問題なくアンシリアライズされます。

リスト unserialize.php(PHP 8.2)
$i = unserialize('i:10;s:5:"Hello";');	// 実行結果:10

 目的は達成されるので問題ないように見えますが、このようなデータが渡されることは、シリアライズ文字列の生成ロジックに何らかの問題がある可能性があるということです。場合によってはセキュリティ上の脆弱(ぜいじゃく)性にもつながる可能性があるとして、PHP 8.3では同様のケースで警告を発生するようになりました。

リスト unserialize.php
$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などが別に発生して、一括のハンドリングが困難でした。

リスト unserialize.php(PHP 8.2)
$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の発生については変更ありません。

リスト unserialize.php(PHP 8.3)
$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数が与えられたときに期待通りの演算結果を得られないという問題がありました。

リスト array_sum_product.php
$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を、今後も見守っていきたいものです。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
ますます便利になるPHPの新機能を探ろう!連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/18888 2024/01/29 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー