PHP重鎮の廣川氏によるコラム「 PHPの最新状況:PHP 8.2β版リリースされる」(第26回)

PHP

廣川類

秋の訪れとともに開発が進むPHP 8.2について、新機能や改善点などについて詳述しています。特に、論理式の手法であるDisjunctive Normal Form(DNF)による型宣言がサポートされることや、センシティブな情報の出力を抑制する機能の追加などが紹介されています。さらに、列挙型プロパティの取得が可能になる点や、true型の追加など、バージョンアップでの変更点や利用の可能性が詳解されています。

 猛暑が続いた夏も終わり,すっかり秋になりました。次のマイナーバージョンであるPHP 8.2の開発も順調に進んでいます.本稿では,前回に続いてPHP 8.2における残りの変更点について紹介します.

PHP 8.2の開発状況

 今年の11月24日に計画されている正式リリースに向けて,PHP 8.2の開発が計画通り,順調に進んでいます. 8月18日に3回目のβリリースが行われ,すでに8月までに全3回のβリリースが行われています.9月以降はリリース候補版が2週間に1回のペースでリリースされる予定であり,9月1日にリリース候補1版(RC 1)がリリースされます.この後,計画通りであれば11月24日にリリースを迎えることになります.読者の皆様もβ版,リリース候補版をテストしていただき,安定した正式版のリリースに向けてご支援いただければと思います.

DNF型のサポート

PHP 8.2では,論理式の標準的手法であるDisjunctive Normal Form (DNF)による型宣言がサポートされます.DNFは,(複数または一つの)AND式をOR式で結合する構造であり,従来のPHP でサポートされている 合併型(Union)と交差型(Intersection)を結合するような型宣言が可能になります.

では,早速例を見てみましょう.以下の例では,インターフェイスA~Dを定義しています.インターフェイスDの戻り値の型は,PHP 8.2で導入されるDNF形式を用いて (A&B)|C,つまり,(AとBのAND)とCのOR として定義されています.ここで,A&Bは丸カッコで括る必要があります.逆にA&Bしか存在しない場合は,丸括弧で括るとエラーになることに注意してください.また,(A|C)&(B|C)のようにOR式をANDで括ることはDNFの原則に反するため,許可されていません.

interface A {}
interface B {}
interface C {}
interface D {
    public function show(): (A&B)|C;
}

 引数でも同様にDNF形式で型を (A&B)|C のように指定できます.

interface D {
    public function show((A&B)|C $a): void;
}

 クラス継承においては,以前説明した「共変性と反変性」の原則が適用されます.戻り値には共変性が適用されます.以下,インターフェイスDを実装するクラスMooの定義を見てみましょう.

interface D {
    public function show(): (A&B)|C;
}
class Moo implements D {
    public function show(): A&B {}
}

 ここで,Mooのshow()メソッドの型は,標準で(A&B)|Cとなりますが,A&Bのように集合を同じ,または,縮小させる型宣言のみが可能です.逆に (A&B)|C|Eのように新たな集合(例:E)を追加することは許されません.

 引数については,反変性,つまり,同等もしくは集合を広くする方向のみが可能です.以下の例を見てみましょう.

interface D {
    public function show((A&B)|C $a): void;
}
class Moo implements D {
    public function show(A|C $a): void {}
}

 この例では,インターフェイスDを実装するクラスMooにおいて,メソッドshowの引数をA|Cと宣言しています.この宣言は,(A&B)|Cよりも制約が緩くなっており,反変性の原則を満たしています.逆に,(A&B)|(C&E)のような制約を厳しくする型宣言を行うとエラーとなります.

 合併型(Union)と交差型(Intersection)を結合するような複合型宣言は,DNF形式のみ可能となります.一般的な複合型宣言もDNF形式に置き換えることが可能ですので,型宣言の自由度が高くなることが期待できます.

バックトレースにおけるセンシティブなパラメータの出力抑制

 PHPでは,ハンドルされない例外やエラーが発生した際に標準ではスタックトレースが表示またはログに記録されます.しかし,この中にパスワードのようなセンシティブな情報が含まれてしまう可能性があります.例えば,データベースの処理を行うPDOエクステンションでは,データベースとの接続認証用のデータ(ユーザ名やパスワード)を引数に入力しますが,その処理でエラーや例外を発生すると,スタックトレース表示にその情報が含まれてしまう可能性があります.以下の簡単な例を見てみましょう.

<?php
function db_connect($user, $password) {
    throw new \Exception('Error');
}
db_connect('taro', 'secret');

 この例では,データベース接続を模擬した関数db_connectで例外を発生させています.この例の出力は以下のようになります.

Fatal error: Uncaught Exception: Error in ../test.php:3
Stack trace:
#0 ../test.php(6): db_connect('taro',’secret’)
#1 {main}
  thrown in test.php on line 3

 パスワード(第二引数)がスタックトレースに含まれています.この表示が画面出力され,もしくは,ログファイルに保存された場合,意図せずにパスワードが漏れてしまうリスクが生じます.なお,実運用システムでは,こうしたリスクを下げるために,例外・エラー処理をWebブラウザに出力する設定は推奨されていないことに注意してください.

 PHP 8.2では,以下のようにセンシティブな引数(ここでは$password)の前にアノテーション #[\SensitiveParameter] を付与することで,対応する引数の内容の出力を抑制することができます.

<?php
function db_connect($user, #[\SensitiveParameter] $password) {
    throw new \Exception('Error');
}
db_connect('taro', 'secret');

出力は以下のようになり,当該変数の値がObject(SensitiveParameterValue) と表示されていることがわかります.

Fatal error: Uncaught Exception: Error in ../test.php:3
Stack trace:
#0 ../test.php(6): db_connect('taro', Object(SensitiveParameterValue))
#1 {main}
  thrown in test.php on line 3

 この状態で,Webブラウザへの出力やログへの記録が行われた場合でも,パスワードのような機密情報の漏洩を回避することができます.

定数式における列挙型プロパティのサポート

この提案は,定数式において列挙型(enum)のプロパティを取得できるようにするものです.この提案は,開発者の投票において24対11と比較的僅差で採用されています.早速,具体例を見てみましょう.

<?php
enum A: string {
    case B = 'B_';
    const C = [self::B->value => self::B]; // A::C['B_'][name->'B',value->'B_']
}

 ここでは,列挙型(enum)の内部の定数式で列挙型のプロパティを参照しています(4行目).ここでは,A::Cは,’B_’をキー,値をB自身とする連想配列となります.列挙型のキー,値にアクセスする際には,例えば,print(A::C[‘B_’].name) により’B’,print(A::C[‘B_’].value)により’B_’が取得されます.

 PHP 8.2より以前のバージョンで上記のコードを実行すると,以下のように定数式に不正な操作が含まれているという致命的エラーが発生します.

Fatal error: Constant expression contains invalid operations in ..

次に,以下のコードを追加してみましょう.

function f($p = A::B->value) { // $p='B_'
    static $q = A::C['B_']->name; // $q='B'
    print($p.$q);
}
f(); // B_B

このコードの出力は ‘B_B’となります.このコードでは,関数f()を定義して,列挙型Aの要素にアクセスしています.引数$pの値を指定せずに関数f()をコールしているため,デフォルト値(A:B->value)が代入されます.また,次の行では,プロパティnameにアクセスしています.これらはいずれも過去のPHPのバージョンでは許可されていなかった処理です.

 続いて,クラスの内部から列挙型のプロパティを呼び出す例を見てみましょう.

class C {
    public string $p = A::B->name;
}
$obj = new C;
print($obj->p);

このコードの出力は,’B’となります.2行目では,パブリック文字列変数$pにA::B->nameの値(’B’)を代入しており,インスタンス化されたクラスオブジェクトのプロパティ$pにアクセスした際に値’B’が出力されます.

 実際のユースケースが分かりづらいかもしれませんが,列挙型を使う際の記述の自由度が上がるのがメリットと言えると思います.

true型のサポート

本連載の23回目でご紹介したようにPHP 8.2では,null型およびfalse型が追加されます.これに加えて,true型もサポートされることが決定しました.当初,論理値としてfalseのみがサポートされた理由は,内部関数の多くにおいて処理に失敗した際にfalse,成功した場合に内部処理に基づく値を返しており,統計的にfalse型のサポートの必要性が高いと判断されたためです.しかし,PHP 8.0でValueError例外がサポートされて以降,処理が失敗した場合でもfalseを陽に返さないデフォルト関数が増えており,逆に論理的一貫性を保つため,論理型(bool)を構成するfalseだけではなく,trueも型として定義することが提案されました.

この提案は採択され,例えば,以下のような記述が可能となっています.

<?php
class A {
    private true $b;
    public function get(int|string|true $c): true {}
}

 上のコードでは,クラスAのプライベートプロパティとしてtrue型の変数$bを定義しています.また,整数,文字列およびtrueの列挙型引数$cおよびtrue型の戻り値のパブリックメソッドgetを定義しています.上記のgetメソッドの定義で,以下のようにtrueとfalseを同時に使用することはできず,エラーを発生します.

public function get(int|string|true|false $c): true {}

この場合の代替案としては,true|falseの代わりにboolを使用します.

 今回は,PHP 8.2における新機能として,DNF型,true型といった新しい型定義およびバックトレース時における出力抑制について紹介しました.次回は,PHP 8.2における互換性に関連する変更点と,過去のバージョンとの実行速度比較について紹介する予定です.

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

関連記事

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

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

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

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

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