PHP8.3が予定通り11月23日にリリースされました。前回のPHP 8.2のリリースから、多くの新機能と改善点が盛り込まれています。公式にはマイナーリリースとなりますが、中には日々のワークフローに直接影響するような変更点も。

PHP 8.3注目の新機能と大小含めたさまざまな変更点をご紹介していきます。

PHP 8.3の新機能と改善点

まずは、PHP 8.3の代表的な新機能と改善点から見ていきましょう。

型付きクラス定数

PHP 7.4でクラスのプロパティの型を宣言する機能が導入されて以降、この型付けは何年にもわたって改善されてきたにもかかわらず、これまで定数には適用されていませんでした。

PHP8.3では、クラス定数(インターフェース、トレイト、列挙型も含む)にも型が書けるように。これによって、意図した最初の宣言からずれにくくなります。

以下は、インターフェースを使用した簡単な例です。

// 良い例
interface ConstTest {
    // 宣言された型と値は両方とも文字列
    const string VERSION = "PHP 8.3";
}

// 悪い例
interface ConstTest {
    // 最初の宣言の型と値が不一致
    const float VERSION = "PHP 8.3";
}

型付きクラス定数の本領が発揮されるのは、ベースとなる宣言から派生したクラスを扱う場合です。子クラスが定数に新しい値を代入することはよくありますが、 PHP 8.3では、誤って型を変更して最初の宣言と互換性がなくなることを回避することができます。

class ConstTest {
    const string VERSION = "PHP 8.2";
}

class MyConstTest extends ConstTest {

    // 良い例
    //ここでVERSIONの値を変更してもOK
    const string VERSION = "PHP 8.3";

    // 悪い例
    // 型がベースとなるクラスで指定されている場合にはそれを宣言しなければならない
    const VERSION = "PHP 8.3";

    // 悪い例
    // この場合、新しい型とその値に互換性があっても
    // ベースクラスで宣言された型を変更することはできない
    const float VERSION = 8.3;
}

クラスの定数に代入される型は、複数の型を「絞り込む」際や、互換性のある型を柔軟に使用する際には変化する可能性があるため、注意が必要です。

class ConstTest {
    const string|float VERSION = "PHP 8.2";
}

class MyConstTest extends ConstTest {

    // 良い例
    // ここでは型宣言を文字列か浮動小数点数に絞ってもOK
    const string VERSION = "PHP 8.3";
    const float VERSION = 8.3;

    // 良い例
    // 値がint型になり得る(floatと互換性はある)
    const float VERSION = 8;

    // 悪い例
    // ここで型の選択肢をint型に広げることはできない
    const string|float|int VERSION = 8;
}

なお、戻り値を検証する際に他のプロパティでサポートされているvoidneverの2つの型は、クラス定数の型としてサポートされていません。

json_validate()関数

JSONでエンコードされたデータを扱う際、文字列が構文的に妥当であるかどうかが事前にわかると効率的です。

以前のバージョンでは、json_decode()関数を使って、JSONデータを連想配列やオブジェクトに変換しようとしている間にエラーが発生しないかどうかを確認するのが通例でした。しかしjson_validate()関数が導入されたことで、今後は配列やオブジェクト構造を構築するために必要なメモリを消費することなく、エラーをチェックすることができるようになります。

これまでは、以下のようにJSON文字列を検証するのが一般的でした。

$obj = json_decode($maybeJSON);

if (json_last_error() === JSON_ERROR_NONE) {
    // $objを使って何かしらの操作を実行
}

上記の例で$objを使用して何も行わない場合、元のJSON文字列の妥当性を検証するためだけに多くのリソースを浪費することになります。PHP8.3では、以下のようにすることで、消費メモリを削減することができます。

if (json_validate($maybeJSON)) {
    // $maybeJSONを使って何かしらの操作を実行
}

json_validate()を使用し、すぐにjson_decode()でデータを実行する場合には、あまりこの関数を使用する意味がありません。代わりに、JSONをどこかに保存したり、リクエストのレスポンスとして配信したりする前に、新しい関数を使用してJSONを検証するケースが考えられます。

readonlyプロパティのディープクローン

PHP 8.1では、個々のクラスプロパティをreadonlyとして宣言する機能が登場し、PHP 8.2では、その属性をクラス全体に割り当てる機能が導入されました。これには、このようなプロパティを含むクラスを扱う際に制約が生じ、時にコーディング作業の妨げになるという課題がありました。

readonlyの挙動を変更するためのRFCでは、以下2つの提案が出ています。

  1. readonlyでないクラスがそうであるクラスを拡張できるようにする
  2. 複製作成時にreadonlyプロパティを再初期化できるようにする

今回のリリースで採用されたのは2つ目の提案で、readonlyプロパティを持つクラスのインスタンスを__cloneマジックメソッド内で再初期化できるようになりました(__clone内から呼び出される関数を含む)。

その動作は以下のとおりです(RFCのコード例)。

class Foo {
    public function __construct(
        public readonly DateTime $bar,
        public readonly DateTime $baz
    ) {}
 
    public function __clone() {
        // cloneが実行されると、$barが新たなDateTimeを取得
        $this->bar = clone $this->bar; 

        // この関数を呼び出す
        $this->cloneBaz();
    }
 
    private function cloneBaz() {
       // これは __clone内から呼び出された場合に妥当
        unset($this->baz); 
    }
}
 
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;

#[\Override]属性

PHPでインターフェースを実装する際、そのインターフェースで名付けられた関数の詳細な機能を指定します。クラスのインスタンスを作成する際、子クラスで同じ名前で互換性のあるシグネチャを持つ別バージョンを作成することで、親関数をオーバーライド(再定義)することができます。

この状況における懸念点として、インターフェースの関数を実装しているつもり、あるいは親関数をオーバーライドしているつもりでも、実際にはできていない可能性があります。子クラスの関数名を誤って入力したり、親クラスのコードで関数を削除してしまったり、名前が変更されたりしたことで、まったく別のものを作成してしまうこともあり得ます。

PHP 8.3では、#[\Override]属性の登場により、関数がコード内で明確に何らかの系統を持たなければいけないようにできます。

以下、簡単な例をご紹介します。

class A {
    protected function ovrTest(): void {}
}

// ovrTest()は親クラスにあるため
// これは機能する
class B extends A {
    #[\Override]
    public function ovrTest(): void {}
}

// ovrBest()が親にないため
// これは失敗(入力ミス)
class C extends A {
    #[\Override]
    public function ovrBest(): void {}
}

クラス定数および列挙型メンバの動的な取得

PHPコードの他のプロパティとは異なり、これまでクラス定数や列挙型を変数名で取得するのはやや複雑でした。PHP 8.3よりも前のバージョンでは、constant()関数が使用されていました。

class MyClass {
    public const THE_CONST = 9;
}

enum MyEnum int {
    case FirstMember = 9;
    case SecondMember = 9;
}

$constantName = 'THE_CONST';
$memberName = 'FirstMember';

echo constant('MyClass::' . $constantName);
echo constant('MyEnum::' . $memberName)->value;

PHP 8.3に追加された定数の動的取得機能を使用すれば、上記のようなクラス定数や列挙型を使って同じ結果を取得することができます。

$constantName = 'THE_CONST';
$memberName = 'FirstMember';

echo MyClass::{$constantName};
echo MyEnum::{$memberName}->value;

getBytesFromString()関数

事前に承認された文字の集合を使用して、ランダムな文字列を生成したいと思ったことがある方は多いはずです。PHP 8.3では、Random拡張モジュールにgetBytesFromString()関数が導入され、これが実現可能になりました。

この関数の使い方はシンプルで、ソースとなる文字列を渡して使用したい文字数を指定すると、指定した長さに達するまで無作為に文字列からバイトを選択します。

簡単な例を見てみましょう。

$rando = new Random\Randomizer();
$alpha = 'ABCDEFGHJKMNPQRSTVWXYZ';

$rando->getBytesFromString($alpha, 6); //  "MBXGWL"
$rando->getBytesFromString($alpha, 6); //  "LESPMG"
$rando->getBytesFromString($alpha, 6); //  "NVHWXC"

要求されたランダムな出力の長さが、入力文字列よりも長いバイト数になる場合もあります。

$rando = new Random\Randomizer();
$nums = '123456';

$rando->getBytesFromString($nums, 10); //  "2526341615"

一意の文字からなる入力文字列では、ランダムな結果が同じ結果で選択されますが、以下のように、入力文字列の中で他の文字よりも多く出現するようにすることで、重み付けすることも可能です。

$rando = new Random\Randomizer();
$weighted = 'AAAAA12345';

$rando->getBytesFromString($weighted, 5); //  "1AA53"
$rando->getBytesFromString($weighted, 10); //  "42A5A1AA3A"

getFloat()およびnextFloat()関数

Random拡張モジュールの改善として、ランダムな浮動小数点値を生成する関数、getFloat()nextFloat()も導入されています。

例を見てみましょう。

$rando = new Random\Randomizer();

// 最小値0から最大値5までの
//  float値を生成する
$rando->getFloat(0,5); // 2.3937446906217

getFloat()では、最小値と最大値の後に3つ目のパラメータも指定可能です。Random\IntervalBoundary列挙型を使用すると、最小値と最大値そのものを関数で返すかどうかを指定することができます。

ルールは以下のとおりです。

  • IntervalBoundary::ClosedOpen:最小値は返すが、最大値は返さない
  • IntervalBoundary::ClosedClosed:最小値と最大値の両方を返す
  • IntervalBoundary::OpenClosed:最小値は返さないが、最大値は返す
  • IntervalBoundary::OpenOpen:最小値も最大値も返さない

3つ目のパラメータに列挙型を指定せずにgetFloat()を使用した場合、IntervalBoundary::ClosedOpenがデフォルトとなります。

公式ドキュメントでは、無作為な経度と緯度の座標を生成する例が紹介されています。緯度には-90と90を含めることができますが、(同じ値になるため)経度には-180と180の両方を含めることはできません。

$rando = new Random\Randomizer();

printf(
    "Lat: %+.6f Long: %+.6f",
    $rando->getFloat(-90, 90, \Random\IntervalBoundary::ClosedClosed),

    // -180 will not be used 
    $rando->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed),
);

nextFloat()は、基本的にgetFloat()を使用して0から1未満のランダムな値を要求するのと同じになります。

$rando = new Random\Randomizer();

$rando->nextFloat(); // 0.3767414902847

その他の細かな改善点

PHP 8.3には他にも細かな変更点が多数あり、以下のような関数も新たに導入されています(リンクより公式ドキュメント参照可能)。

非推奨の機能

新たなバージョンにはさまざまな新しい機能や改善点が導入される一方で、非推奨となる関数や設定もあります。非推奨の機能は、継続的な使用は推奨されず、実行中のコードに使用すると多くのログに通知が表示されるようになります。

PHP 8.3では、以下の機能が非推奨となります。

まとめ

PHP 8.3の重要な変更点と新機能をご紹介しました。今回のバージョンのすべての更新に関する情報は、PHPの公式ドキュメントをご覧ください。また、最新バージョンのPHPをサポートするプラットフォームにコードを移行する場合は、PHP 8.2から8.3に移行する方法が役に立ちそうです。

PHP 8.3は、開発・運用サーバーにダウンロードおよびインストール可能です。

KinstaのWordPress専用マネージドホスティング、およびアプリケーションホスティングでもまもなくPHP 8.3をサポート予定です。

Steve Bonisteel Kinsta

Kinstaのテクニカルエディター。救急車や消防車を追いかける記者としてキャリアをスタート。1990年代後半からインターネット関連の技術情報を担当している。