PHP重鎮の廣川類氏のコラム第33回「PHP 8.3ついにリリース」

PHP

廣川類

PHP 8.3の正式リリースとその新機能を紹介。型指定可能なクラス定数、強化されたReadonly機能など開発者待望の変更点が加わり、過去のバージョンとの互換性も記述。ますます使いやすくなったPHP 8.3の詳細な変化点について知ろう。

 秋に入っても20度を超える日が続いていましたが,年末が近づき本格的に寒くなる日も増えてきました。PHP 8.3の開発は順調に行われ,予定通り,11月23日(日本時間は11月24日)に正式公開されました.本連載では,PHP 8.3における変更点について紹介してきましたが,今回の記事では,今一度,代表的なものを紹介するとともに,過去のバージョンとの互換性について記述したいと思います.

PHP 8.3における主な変更点

 PHP 8.3は,PHP 8における3回目のマイナーバージョンアップになり,基本的な機能に関する変更点はありませんが,より使いやすくするという意味での改良が行われています.主な変更点を以下に示します.

  • クラス定数の強化
    – 型指定ができるようになりました.

– 動的変数で参照できるようになりました.

  • Readonly 機能の強化
    – 匿名クラス,クローン時に利用可能で利用可能となりました.
  • アトリビュートによるオーバーライド指定
    – 親クラスに対応するメソッドがあることが保証されるようになりました.
  • Randomizerへのメソッド追加
    – getBytesFromStringメソッドが追加されました.
  • json_validate関数の追加
  • json_decode関数の検証部分が関数化されました.

これらの変更点については,過去数回の記事で紹介していますので,詳細はそれらの記事を参照してください.本稿では,過去のバージョンとの互換性に関する変更点について紹介します.

unserialize()のエラー整合性を改善

PHPでは,unserialize()関数によりシリアル化されたパラメータをデコードすることができます.この際,不正な文字列を入力した場合の動作が変更されます.

 例えば,以下のコードを実行してみましょう.

<?php
unserialize('foo');

 不正な文字列が入力された場合,PHP 8.2までのバージョンでは以下のように通知(Notice)が発生します.

Notice:  unserialize(): Error at offset 0 of 3 bytes

PHP 8.3では,以下のようにより厳しい警告を発生します.

Warning: unserialize(): Error at offset 0 of 3 bytes

 なお,PHP 9.0以降では,専用の例外としてUnserializationFailedException を発生するよう動作が変更される予定です.

 また,以下のようにシリアル化処理が正しく終了していない,または末尾が欠損している場合を考えてみましょう.

unserialize('O:19:"SplDoublyLinkedList":3:{i:0;i:0;i:1;N;i:2;a:0:{}}');

このコードを実行すると,以下のように UnexpectedValueException 例外が発生します.この動作はPHP 8.2までのバージョンでも,PHP 8.3でも変わりません.

PHP Fatal error:  Uncaught UnexpectedValueException: Incomplete or ill-typed serialization

今後,こちらについても,PHP 9.0で専用の例外(UnserializationFailedException)を発生するように動作が変更される予定です.

 有効なシリアル化データの末尾に不正なバイトがついていた場合,PHP 8.2までのバージョンでは,有効な部分をシリアル化していて,取得できていました.例えば,以下のコードを実行してみましょう.

<?php
var_dump(unserialize('i:5;i:6;'));

末尾の ‘i:6;’ は不要な文字列で,PHP 8.2までのバージョンでは,int(5) として取得されます.しかし,この動作を悪用した攻撃が想定されるため,PHP 8.3ではこのようなケースで以下のような警告が発生するようになりました.

Warning: unserialize(): Extra data starting at offset 4 of 8 bytes

range()関数の一貫性改善

指定した範囲の配列を取得する際に range() 関数が使用されます.このrange()関数の動作が変更され,エラー処理を含めた動作の一貫性が向上しています.

まず,以下のコードを実行してみましょう.

<?php
var_dump(range(1, 3, 1.0));

上記のコードを実行すると,PHP 8.2までのバージョンでは以下のように出力されます.

array(3) {
  [0]=> float(1)
  [1]=> float(2)
  [2]=> float(3)
}

第1引数に下限,第2引数に上限を指定し,この場合は,1から3までの値を要素とする配列が作成されます.ステップは標準で1ですが,3番目の引数(ステップ)にオプションで値を指定することができます.ここでは, float  (1.0)が指定されており,ステップに float を指定することにより,すべて,float として処理されることがわかります.

 一方,PHP 8.3以降では,3番目の引数(ステップ)にfloatを指定した場合,整数にした時と値が同じ場合は,intとみなされるようになります.上記の例では,ステップにfloat (1.0)を指定していますが,int にキャストした場合(1)でも,同じ値となるため,int とみなされます.このため,PHP 8.3で上記のコードを実行すると,以下のように整数の配列が返されます.

array(3) {
  [0]=> int(1)
  [1]=> int(2)
  [2]=> int(3)
}

 

 次に以下のコードを実行してみましょう.

<?php
var_dump(range("9", "A"));

 PHP 8.2までのバージョンの出力は以下となります.

array(10) {
  [0]=> int(9)
  [1]=> int(8)
  [2]=> int(7)
  [3]=> int(6)
  [4]=> int(5)
  [5]=> int(4)
  [6]=> int(3)
  [7]=> int(2)
  [8]=> int(1)
  [9]=> int(0)
}

 この出力をコードから予想できた人は少ないのではないでしょうか?PHP 8.2までのバージョンの動作では,引数に文字列を指定しても,intにキャストされます.”9”,”A”はそれぞれ,9,0 に変換されます.このため, array (9, 0) が実行されることになり,9 から 0までの整数値が逆順に配列の値として取得されます.

 PHP 8.3以降では,文字を指定すると,そのまま文字として評価され,ASCIIコード表の順番に範囲が設定されます.PHP 8.3で実行した場合の出力を見てみましょう.

array(9) {
  [0]=>  string(1) "9"
  [1]=>  string(1) ":"
  [2]=> string(1) ";"
  [3]=> string(1) "<"
  [4]=> string(1) "="
  [5]=> string(1) ">"
  [6]=> string(1) "?"
  [7]=> string(1) "@kusanagi
  [8]=> string(1) "A"
}

 ASCIIコード表で“9”(0x39)から”A”(0x41)の範囲にある文字を値とする配列が返されます.

 エラー処理もより厳格になります.従来のPHPで,第3引数に不正な指定をした場合,デフォルト値が使用されて実行されていましたが,PHP 8.3以降ではエラーを発生します.以下のコードを実行してみましょう.

<?php
var_dump(range(0, 3, -1));

 第1引数よりも第2引数の方が大きいため,第3引数には正の数値を指定する必要がありますが,この例では負の数(-1)が指定されています.PHP 8.2までは,不正な値が指定された場合は,デフォルト値(1)が使用されるため,出力は以下となります.

array(4) {
  [0]=> int(0)
  [1]=> int(1)
  [2]=> int(2)
  [3]=> int(3)
}

 PHP 8.3では以下のようにエラーとなります.

PHP Fatal error:  Uncaught ValueError: range(): Argument #3 ($step) must be greater than 0 for increasing ranges

マルチ関数シグニチャの廃止

PHPでは,関数シグニチャが複数存在する関数があります.その多くは歴史的な理由によりますが,一貫性がないものがあり,わかりにくさにつながっていました.また,デバッグが複雑になったり,関数シグニチャからドキュメントを自動生成するツールにおいて問題を発生したりすることが指摘されてきました.

 PHP 8.3以降,これらの複数の関数シグニチャを統一し,一つにする方向とする提案が行われ,採択されました.統一にあたり,廃止されるものが引き続き使用される可能性がある場合には,代替の関数が定義されます.

統一(廃止)は以下のように段階的に行われます.

  1. PHP 8.3:要すれば代替関数を定義する
  2. PHP 8.4:廃止対象(DEPRECATED)に指定する
  3. PHP 9.0または10.0で廃止する

いくつかの例をみてみましょう.まず,PostgreSQLエクステンションで結果を取得する pg_fetch_result() です.この関数には,以下の2種類の関数シグニチャが定義されています.

function pg_fetch_result(PgSql\Result $result, mixed $field): string|false|null {}
function pg_fetch_result(PgSql\Result $result, int $row, mixed $field): string|false|null {}

2番目のシグニチャが最終的に廃止される予定で,PHP 8.3では,このシグニチャの第2引数が以下のように nullable に変更されます.

function pg_fetch_result(PgSql\Result $result, ?int $row, mixed $field): string|false|null {}

 この後,PHP 8.4において第2引数が廃止対象(DEPRECATED)の告知を発生するようになり,PHP 9.0以降で廃止される予定です.

 同様の変更(第2引数の廃止)は,pg_field_prtlen(),pg_field_is_null()関数についても行われます.

 次に,DatePeriodクラスについて紹介します.このクラスのコンストラクタは以下のように3種類定義されています.

class DatePeriod
{
    public function __construct(DateTimeInterface $start,
DateInterval $interval, int $recurrences, int $options = 0) {}
    public function __construct(DateTimeInterface $start,
DateInterval $interval, DateTimeInterface $end, int $options = 0) {}
    public function __construct(string $isostr, int $options = 0) {}
}

 将来的に1番目と2番目のシグニチャが以下のように統合され,3番目は廃止されます.

class DatePeriod
{
    public function __construct(DateTimeInterface $start,
DateInterval $interval, DateTimeInterface|int $end,
int $options = 0) {}
}

廃止される3番目のシグニチャについては,代替手段として static メソッド createFromISO8601Stringが用意されます.

class DatePeriod
{
    public static function createFromISO8601String(
string $specification, int $options = 0): static {}
}

 PHP 8.3では,3番目のシグニチャに関する代替手段として static メソッド createFromISO8601Stringが用意されました.今後,PHP 8.4で引数が1個または2個の場合に廃止対象(DEPRECATED)が発生するようになり,PHP 9.0 または 10.0で廃止される予定です.

その他の廃止項目

いくつかの機能が廃止対象となりますので,主なものを紹介します.

mb_strimwidthの負の引数廃止

まず,mbstringエクステンションの関数mb_strimwidth の第3引数に負の引数を指定する機能が廃止されます.以下例をみてみましょう.

echo mb_strimwidth('abcde', 0, -1);

出力は,’abcd’ となります.3番目の引数はトリムする文字数を意味しますが,負の数を指定すると文字列の末尾からの幅を意味するようになります.しかし,この機能は2016年に追加されたものの,意図がわかりにくく,また,ユースケースが限定的で,現存するオープンなコードにおいても実際に使用されていないことがわかったため,廃止されることになりました.

PHP 8.3では,以下のようなDEPRECATED通知を発生します.今後のバージョンで廃止される予定です.

Deprecated: mb_strimwidth(): passing a negative integer to argument #3 ($width) is deprecated

mt_range()関数における後方互換モード廃止

PHP では,メルセンヌツイスターによる乱数生成をサポートしていますが,PHP 7.1より前のバージョンでは生成される乱数に偏りがあるなどの問題が発生し,PHP 7.1で修正されています.後方互換性のため,従来の実装を引き続き利用するユーザに対して,定数MT_RAND_PHPを指定することで,従来の問題のある実装を引き続き利用することが可能となっていました.PHP 8.2で導入されたオブジェクト指向の新しい乱数生成機能(Randomエクステンション)でもこの互換モードは利用可能となっています.しかし,新しいオブジェクト指向APIで過去の動作を完全に再現することは難しく,また,ユースケースも限られていました.

 今回の提案は,MT_RAND_PHP を廃止対象とするもので,問題のあるメルセンヌツイスターの実装は利用できなくなります.PHP 8.3でMT_RAND_PHP を指定すると,以下のようなDEPRECATED通知を発生します.今後のバージョンで廃止される予定です.

Deprecated: The MT_RAND_PHP variant of Mt19937 is deprecated

 今回は,前回に続いて,PHP 8.3における変更点を紹介しました.基本的な動作はPHP 8.2までのバージョンと互換性がありますが,従来,統一されていなかった動作が統一されたりしており,後方互換性について一部注意が必要なところもあります.利用するPHPコードにより十分確認を行った上で,運用ソフトウエアのバージョン更新をしていただくと良いでしょう.

さらにパワーアップしたPHP 8.3をぜひ利用していただければと思います.

<< PHP重鎮の廣川類氏のコラム第32回「PHPの最新状況:PHP 8.3の改善点」

関連記事

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

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

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

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

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