PHP8.3の変更点をチェックしよう

:::note
[ゆるWeb勉強会@札幌 Advent Calendar 2023](https://adventar.org/calendars/8767) ２日目
:::

# この記事について

PHPのバージョン8.3が先日2023年11月23日にリリースされました。このバージョンアップではどのような点が変更されたのか、個人的に気になったところをピックアップして紹介していきます。

https://www.php.net/releases/8.3/ja.php

## 変更点ピックアップ

## 新機能
### クラス定数の型付け

クラス定数に型を書けるようになりました。

```php:PHP8.3
class HogeClass {
    const string A = 'hoge';
}

interface HogeInterface {
    const string A = 'hoge';
}

trait HogeTrait {
    const string A = 'hoge';
}

enum HogeEnum {
    const string A = 'hoge';
}
```

以前は型が書けなかったので、extendsやimplimentsした際に異なる型の値で初期化してしまうことができました。型を明示してそういった事態を防ぐことができるので、ありがたい変更です。

```php:PHP < 8.3
interface I {
    const PHP = 'PHP 8.2';
}

class Foo implements I {
    const PHP = []; // 😨
}
```

### Override アトリビュート

PHP8から[アトリビュート](https://www.php.net/manual/ja/language.attributes.overview.php)が実装されましたが、今回そこに`#[\Override]`が追加されました。これにより、メソッドがオーバーライドしたものであることを明示することができます。

```php:PHP8.3
class C1 {
    public function hoge(): void {}
}

class C2 extends C1 {
    #[\Override]
    public function hoge(): void {}
}
```

これにより、親クラスやインターフェースに同じメソッドが定義されていない場合はエラーが発生します。例えば、メソッド名をtypoしてしまったり、元のメソッドが変更された場合に気がつくことができそうです。

```php
class C1 {
    public function hoge(): void {}
}

class C3 extends C1 {
    #[\Override]
    public function hogee(): void {}
}

// Fatal error: C3::hogee() has #[\Override] attribute,
// but no matching parent method exists
```

### クラス定数への動的なアクセス構文

クラス定数に対して動的にアクセスできるようになりました。

```php:PHP8.3
class Sample {
    const HOGE = 'hoge';
}

$value = 'HOGE';
$getValue = fn() => 'HOGE';

var_dump(Sample::{$value}); // hoge
var_dump(Sample::{$getValue()}); // hoge
```

合わせて列挙型/Enumの値にも動的にアクセスできるようになりました。以前はシンプルにアクセスする方法がなかったので、これは嬉しい変更です。

```php:PHP8.3
enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

$param = 'Hearts';
var_dump(Suit::{$param}->value); // H
```

## 新しく追加された関数

### json_validate

文字列が有効なJSONかどうかを調べる[json_validate](https://www.php.net/manual/ja/function.json-validate.php)が追加されました。検証自体は[json_decode](https://www.php.net/manual/ja/function.json-decode.php)でもできるのですが、検証だけでデコードされた使わない場合はこちらのほうがパフォーマンスに優れるようです。例えば、単体テストで検証をたくさん実行するケースに使えそうですね。

```php:PHP8.3
var_dump(json_validate('{ "test": { "foo": "bar" } }')); // true
var_dump(json_validate('{ "": "": "" } }')); // false
```

### mb_str_pad

文字列に対して特定の文字列で埋めて固定長にする[str_pad](https://www.php.net/manual/ja/function.str-pad.php)のマルチバイト版として[mb_str_pad](https://www.php.net/manual/ja/function.mb-str-pad.php)が追加されました。

```php:PHP8.3
var_dump(mb_str_pad('▶▶', 6, '❤❓❇', STR_PAD_RIGHT)); // string(18) "▶▶❤❓❇❤"
var_dump(mb_str_pad('▶▶', 6, '❤❓❇', STR_PAD_LEFT));  // string(18) "❤❓❇❤▶▶"
var_dump(mb_str_pad('▶▶', 6, '❤❓❇', STR_PAD_BOTH));  // string(18) "❤❓▶▶❤❓"

var_dump(mb_str_pad("🎉", 3, "祝", STR_PAD_LEFT));   // string(10) "祝祝🎉"
```

### Random\Randomizer::getBytesFromString

PHP8.2から乱数生成のためのrandom拡張モジュールが追加されましたが、今回はそこに、指定した文字列からランダムな文字列を生成する[getBytesFromString](https://www.php.net/manual/ja/random-randomizer.getbytesfromstring.php)が追加されました。

```php:PHP8.3
$randomizer = new \Random\Randomizer();

echo $randomizer->getBytesFromString(
    'abcdefghijklmnopqrstuvwxyz0123456789',
    10,
);
// z0cshw5me7
```

これだけで暗号的にも安全なランダム文字列が生成できるのは便利ですね。

## 下位互換性のない変更点

### 空の配列に負のインデックスを割り当てた場合の挙動

空の配列に負のインデックスを割り当てた場合に次のインデックスは必ずそこから+1されていく、とのことです。具体的には次のようになります。

```php:PHP8.3
$arr = [];
$arr[-10] = 'a';
$arr[] = 'b';
$arr[] = 'c';
var_dump($arr);
// [
//    -10 => "a",
//    -9 => "b",
//    -8 => "c"
// ]
```

ちなみに以前はどうだったかというと、負のインデックスを割り当てても次は0になり、そこから+1されていきます。

### range関数に対するさまざまな変更

ある範囲の要素を含む配列を作成する[range](https://www.php.net/manual/ja/function.range.php)関数ですが、変更を一言で表すと「より自然な挙動をするようになった」という感じでしょうか。

大まかに２つに分けると、１つは、より自然な配列が生成されるようになった点があります。

```php:より自然な配列を生成
// PHP < 8.3
range('9', 'A'); // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

// PHP 8.3
range('9', 'A'); // ["9", ":", ";", "<", "=", ">", "?", "@", "A"]
```

もう１つは、一般的でない引数が指定された時にエラーが発生するようになりました。

```php:一般的でない引数に対してエラーが発生する
// PHP 8.3
range(1, []); // TypeError
range(1, 10, 0); // ValueError
range(1, 10, -1); // ValueError
```

## 推奨されなくなる機能

### mb_strimwidth関数に負の`width`を渡すこと

文字列を指定した幅に丸める[mb_strimwidth](https://www.php.net/manual/ja/function.mb-strimwidth.php)ですが、その幅を指定する引数`width`には負の値を指定することができます。今回の変更により、負の値を指定するとDeprecatedエラーが発生するようになりました。

```php:PHP8.3
echo mb_strimwidth('abcde', 0, -1);
// Deprecated: mb_strimwidth(): passing a negative integer to argument #3 ($width) is deprecated
// abcd
```

「指定した幅に丸める」というのが目的なので、負の値を指定するのは目的に合わないですね。

# まとめ

先日（2023年11月23日）リリースされたPHPのバージョン8.3について、いくつか変更点をピックアップしてみました。型システムの強化や不自然な挙動の調整が実施され、引き続き安全性が高まっている印象です。他にも変更点はいろいろあるので、移行ガイドから一読してみてはいかがでしょうか。

https://www.php.net/manual/ja/migration83.php

さて、新しいバージョンがリリースされたということはつまり、古いバージョンのサポート終了が迫っているということです。[^1] 8.0は終わりを迎え、8.1はあと１年、8.2はあと２年を切りました。慌てることのないよう、余裕を持って新しいバージョンへ移行しましょう（戒め）。
[^1]:https://www.php.net/supported-versions.php

https://www.php.net/manual/ja/migration81.php

https://www.php.net/manual/ja/migration82.php


# 謝辞

今回PHP8.3を試すためにDockerイメージを待っていたのですが、プルリクエストでこのような発言がありました。

> あるいは、少し我慢して待つという手もある。～中略～ 例えば、家族や友人と過ごしたり、自由な時間を楽しんだりするために、メンテナはコンピュータから離れる時間を作ることができます。（機械翻訳）[^2]

[^2]:https://github.com/docker-library/php/pull/1464#issuecomment-1827278056

メンテナーに感謝と敬意を払う姿勢は忘れたくないものです。PHPとDockerイメージのメンテナーの皆様ありがとうございます。