PHP 8.3の新機能と実務での使いどころ

※本ページには広告(アフィリエイトプログラム等)が含まれます。

PHP 8.3がリリースされてしばらく経つが、さくらのVPSで新規にアプリを組む際はもうこのバージョンを基準にしていい時期だ。8.0のUnion Typesや8.1のEnumのような派手な目玉機能は少ないものの、実務で地味に効いてくる改善がいくつか入っている。ここでは誇張なしに、実際に何が変わったのか、どこで使うと得かを整理する。

Typed class constants(クラス定数の型宣言)

これまでクラス定数には型を書けなかったが、8.3からはプロパティと同じ感覚で型を指定できるようになった。

// before(8.2まで)
class Status
{
    const ACTIVE = 'active';
}

// after(8.3)
class Status
{
    const string ACTIVE = 'active';
}

子クラスで定数をオーバーライドする際、型が違う値を代入しようとするとTypeErrorになる。定数のつもりで書いたコードが後から別の型の値に差し替えられて事故る、というケースを型チェックの段階で防げるようになった。設定値やステータス定数を多用するアプリでは地味に安心材料が増える。

json_validate() — 妥当性だけを省メモリで検証する

受け取ったJSON文字列が構文として正しいかどうかだけを知りたい場面は多い。従来はjson_decode()してエラーの有無を見るしかなく、値が大きいと不要なPHP値への変換コストがかかっていた。8.3のjson_validate()はデコードせずに構文チェックだけを行う。

if (!json_validate($rawBody)) {
    throw new InvalidArgumentException('invalid json payload');
}
$data = json_decode($rawBody, true);

APIのリクエストボディを受けるエンドポイントで、「まず妥当性だけ確認してから本処理に回す」というパターンに向く。数十MB単位の大きなJSONを扱うバッチ処理では、デコードを2回(検証用と本処理用)走らせるより明確にメモリを節約できる。

#[\Override] 属性 — オーバーライドのつもりが実は別メソッドだった、を検出する

親クラスやインターフェースのメソッドをオーバーライドしているつもりで、メソッド名をタイプミスしたまま気づかず放置してしまう、というのはよくある事故だ。8.3では#[\Override]属性を付けておくと、親に同名メソッドが存在しない場合にコンパイル時エラーになる。

class BaseRepository
{
    public function findById(int $id): ?array
    {
        // ...
        return null;
    }
}

class UserRepository extends BaseRepository
{
    #[\Override]
    public function findById(int $id): ?array
    {
        // 正しくオーバーライドできている
        return [];
    }
}

もしfindByldのようにタイプミスしていたら、その場でエラーになって気づける。継承階層が深いフレームワーク寄りのコード(Laravelのサービスクラスやリポジトリパターンなど)で、地味に事故防止効果が高い。既存コードに一気に付けるのではなく、新規に書くクラスから徐々に付けていくのが現実的だ。

動的なクラス定数フェッチ Foo::{$name}

プロパティやメソッドはすでに$obj->{$name}のような動的アクセスができたが、クラス定数は文字列変数で動的に参照できなかった。8.3からFoo::{$name}という書き方ができるようになった。

class Config
{
    const string ENV_PROD = 'production';
    const string ENV_DEV = 'development';
}

$key = 'ENV_' . strtoupper($mode);
$value = Config::{$key};

これまではconstant(Config::class . '::' . $key)のような回りくどい書き方をしていたところが、素直な構文で書けるようになった。定数名をキーにした設定の切り替えロジックがある場合に地味に読みやすくなる。

Randomエクステンションの拡張

8.2で導入されたRandom\Randomizerクラスに、8.3でメソッドが追加された。文字列からランダムなバイト列を取り出すgetBytesFromString()と、指定範囲の浮動小数点乱数を返すgetFloat()などだ。

$randomizer = new \Random\Randomizer();

// 文字集合からランダムな文字列を生成(招待コードなどに使える)
$code = $randomizer->getBytesFromString('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 8);

// 0.0〜1.0の範囲でランダムな小数を取得
$rate = $randomizer->getFloat(0.0, 1.0);

rand()mt_rand()のグローバル関数と違い、Randomizerは特定のエンジン(暗号論的に安全なものも選べる)を明示的に指定できるので、招待コードやトークンの生成では従来のグローバル関数よりこちらを使う理由がはっきりしている。

負のインデックスを持つ配列の挙動変更

配列に負のキーを明示的に設定した状態で要素を追加すると、8.2までは次の自動採番キーが0に戻ってしまい、直感に反する挙動になっていた。8.3ではこれが修正され、負のキーの次は連続する負の数(あるいは0)になるよう調整された。

$arr = [-5 => 'a'];
$arr[] = 'b';
// 8.2まで: $arr[0] === 'b'
// 8.3から: $arr[-4] === 'b'

この挙動に依存したコードは元々あまり無いはずだが、負のIDやオフセットを配列キーに使う実装がある場合は、バージョンアップ時に一度動作確認しておいた方がいい。他にも非推奨化(deprecation)がいくつか入っているので、既存アプリを8.3に上げる際はerror_reportingを一時的にE_ALL相当にしてログを確認しておくと安心だ。

DebianへのPHP 8.3導入は Sury リポジトリが定番

Debian 12(bookworm)の標準リポジトリに入っているPHPはこれより古いバージョンで、8.3を素直に使いたい場合はSury(deb.sury.org)のサードパーティリポジトリを使うのが定番の運用だ。

apt install -y apt-transport-https lsb-release ca-certificates curl
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
    | tee /etc/apt/sources.list.d/php.list
apt update
apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-mbstring php8.3-xml

本家のリポジトリではないので、導入前に自分の運用ポリシーとして許容できるか一度検討しておくのが筋だ。ただ実務上はさくらのVPS含め、多くの現場でSuryを使うのが事実上の標準になっている。

まとめ

PHP 8.3は言語仕様として派手な追加は少ないが、Typed class constantsと#[\Override]属性はコードの堅牢性を上げる方向の地味な改善で、既存プロジェクトを移行するだけでも恩恵がある。json_validate()やRandomizerの追加メソッドは、該当する処理を書くときに知っていれば使う場面がはっきりしている機能なので、ライブラリの内部実装を読むときにも覚えておくと役立つ。

読んで頂いて有り難うございます!