PHP8.3の新機能について（part1）

# 概要
まもなくphp8.3がリリースされますね。
事前にどんな機能が増えるか気になり、先日[PHPerのための「PHP8.3 の新機能について語り合う」PHP TechCafe](https://rakus.connpass.com/event/290984/)に参加しました。
自分のメモ用とアウトプットとを兼ねて、Qiitaに残したいと思います。



## [Marking overridden methods (#[\Override])](https://wiki.php.net/rfc/marking_overriden_methods)

オブジェクトを継承していることを示すアトリビュート #[\Override] が追加されます。
主に下記のようなメリットがあります。

- インターフェースを実装しているのか、クラスを継承してオーバーライドしているのかを明示できる
- 親クラスのシグニチャが変わった場合などで意図しないオーバーライドにならないようにできる


例えば、フレームワークやライブラリのバージョンアップ時に破壊的な変更が加わった際の影響を調べやすくなります。


```
// 使用例
class P {
    protected function p(): void {}
}
 
class C extends P {
    #[\Override]
    public function p(): void {}
}
```

```
// エラーが起きる例
class P {
    private function p(): void {}
}
 
class C extends P {
    #[\Override]
    public function p(): void {} // Fatal error！！！
    // C::p() には #[\Override] アトリビュートがあるが 親クラスにオーバーライド元メソッドがない
}
```



## [Typed Class Constants](https://wiki.php.net/rfc/typed_class_constants)

クラス、インターフェイス、トレイト、および enum の定数に型を設定できるようになります。

```
enum E {
    const string TEST = "Test1";   // E::TEST is a string
}
 
trait T {
    const string TEST = E::TEST;   // T::TEST is a string too
}
 
interface I {
    const string TEST = E::TEST;   // I::TEST is a string as well
}
 
class Foo implements I {
    use T;
 
    const string TEST = E::TEST;  // Foo::TEST must also be a string
}
 
class Bar extends Foo {
    const string TEST = "Test2";  // Bar::TEST must also be a string, but the value can change
}
```
クラス定数は共変であるため、継承しているクラス定数の型を拡張することはできません。

```
trait T {
    public const ?array E = [];
}
 
class Test {
    use T;
 
    private const int A = 1;
    public const mixed B = 1;
    public const int C = 1;
    public const Foo|Stringable|null D = null;
 
    // This is illegal since the type cannot change when T::E is redefined
    public const array E = [];
}
 
class Test2 extends Test {
    // This is legal since Test::A is private
    public const string A = 'a';
 
    // This is legal since int is a subtype of mixed
    public const int B = 0;
 
    // This is illegal since mixed is a supertype of int
    public const mixed C = 0;
 
    // This is legal since Foo&Stringable is more restrictive than Foo|Stringable
    public const (Foo&Stringable)|null D = null;
}
 
enum E {
    // This is legal since constants provide a covariant context
    public const static A = E::Foo;
 
    case Foo;
}
 
class Foo implements Stringable {
    public function __toString() {
        return "";
    }
}
```

## [mb_str_pad](https://wiki.php.net/rfc/mb_str_pad)

[str_pad()](https://www.php.net/manual/ja/function.str-pad.php) のマルチバイト文字対応版です。

```
// This will pad such that the string will become 10 bytes long.
var_dump(str_pad('Français', 10, '_', STR_PAD_RIGHT));   // BAD: string(10) "Français_"
var_dump(str_pad('Français', 10, '_', STR_PAD_LEFT));    // BAD: string(10) "_Français"
var_dump(str_pad('Français', 10, '_', STR_PAD_BOTH));    // BAD: string(10) "Français_"
 
// This will pad such that the string will become 10 characters long, and in this case 11 bytes.
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_RIGHT));// GOOD: string(11) "Français__"
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_LEFT)); // GOOD: string(11) "__Français"
var_dump(mb_str_pad('Français', 10, '_', STR_PAD_BOTH)); // GOOD: string(11) "_Français_"
```




## [Dynamic class constant fetch](https://wiki.php.net/rfc/dynamic_class_constant_fetch)

クラス定数を動的に指定することができるようになります。
```
class Foo {
    const BAR = 'bar';
}
$bar = 'BAR';
 
// PHP8.3 以降は以下の記述が可能
echo Foo::{$bar}; 
 
//　PHP8.2 までで上記と同様の動作を実現する方法
echo constant(Foo::class . '::' . $bar);
```

未定義のクラス定数にアクセスすると Error が発生します。

```
class Foo {}
 
$bar = 'BAR';
echo Foo::{$bar};
Enum の使い勝手向上に期待。

enum Suit:string{
    case Hearts = 'ハート';
    case Diamonds = 'ダイヤ';
    case Clubs = 'クラブ';
    case Spades = 'スペード';
}

echo Suit::{$_REQUEST['suit']}?->value; // ハート
```

## [Arbitrary static variable initializers](https://wiki.php.net/rfc/arbitrary_static_variable_initializers)
これまでのstatic変数の構文では、初期化には固定値しか入れられませんでした。
その制限を緩めて、static変数の初期化時に変数や関数を渡せるようになります。
```
function bar() {
    echo "bar() called\n";
    return 1;
}

function foo() {
    static $i = bar();
    echo $i++, "\n";
}

foo();    
    // bar() called 
    // 1
foo();    
    // 2
foo();
    // 3
```

## [Readonly amendments](https://wiki.php.net/rfc/readonly_amendments)

readonlyプロパティをcloneするときに再初期化することが可能になります。

```
// __clone（）の実行中のみ、readonlyプロパティを再初期化することができる
class Foo {
    // コンストラクタ
    public function __construct(
        public readonly DateTime $bar,
        public readonly DateTime $baz
    ) {}
 
    // clone
    public function __clone()
    {
        $this->bar = clone $this->bar; // OK
        $this->cloneBaz();
    }
 
    private function cloneBaz()
    {
        // __cloneから呼び出されている場合はreadonlyプロパティの変更がOK
        unset($this->baz); 
    }
}
 
$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;
// エラーは発生しない。
// この場合、Foo2::$bar は2重にcloneされており、Foo2::$baz は初期化されない
```

しかし、__cloneメソッドでreadonlyプロパティを変更できるのは1回のみです。
以下のようにTest::$barを2回変更しようとした場合はエラーが発生します。

```
class Test {
    public function __construct(
        public readonly DateTime $bar
    ){}

    public function __clone()
    {
        $this->bar = $this->bar; // OK
        $this->bar = clone $this->bar; // NG
    }
}
```

## [PDO driver specific sub-classes](https://wiki.php.net/rfc/pdo_driver_specific_subclasses)

各ドライバ固有のメソッドを持つPDOのサブクラスが追加されます。
PDOクラスに各データベースに特化したサブクラスが追加され、データベース専用の機能がサブクラスで実行可能になります。
※これまでのnew PDO()も今まで通りの使い方で使い続けることができます。

```
// MySQL
$pdoMySQL = new PdoMySql($dsn);
$pdoMySQL->getWarningCount(); // MySQL専用機能

// Postgres
$pdoPgsql = new PdoPgsql($dsn);
$pdoMySQL->getPid(); // Postgres専用機能

// PDOはこれまでと同じ
$pdo = new PDO($dsn);
```

特定のサブクラスを作成できるPDO::connectが追加されます。
DBの接続を行い、接続したDBの種類に合わせて特定のサブクラスのオブジェクトが返り値として渡されます。
そのため、DSNがPostgreSQLに接続した場合はPdoPogsqlが、MySqlに接続した場合はPdoMySqlが返却されます。

```
class PDO
{
    public static function connect(string $dsn [, string $username [, string $password [, array $options ]]]) {
 
        if (connecting to SQLite DB) {
            return new PdoSqlite(...);
        }
 
 
        return new PDO(...);
    }
}
```
また、以下のように各DBのサブクラスのコンストラクタを使って直接接続することも可能です。

```
$db = new PdoSqlite($dsn, $username, $password, $options);
```

## 感想
一旦今回のアウトプットはここまでにしたいと思います。
個人的には「Marking overridden methods」と「Dynamic class constant fetch」が便利そうなので、PHP8.3がリリースされたら使ってみたいところです。
次回part２で残りの新機能についてまとめたいと思います。