LoginSignup
42
19
お題は不問!Qiita Engineer Festa 2023で記事投稿!

【PHP8.3】PHP8.3の新機能

Last updated at Posted at 2023-07-17

PHP8.3 / PHP8.2 / PHP8.1 / PHP8.0

2023/11/23にリリースされました

2023/07/18、PHP8.3がフィーチャーフリーズしました。
言語機能に関わるような機能の追加・変更が締め切られたということです。
今後はデバッグを繰り返しながら完成度を高めていき、2023/11/23にPHP8.3.0がリリースされる予定です。

というわけでPHP8.3で実装されるRFCを見てみましょう。

RFC

Marking overridden methods (#[\Override])

賛成22反対1で受理。

Overrideアトリビュートです。

class C1 {
    protected function foo(): void {}
}

class C2 extends C1 {
    #[\Override]
    public function foo(): void {} // OK
}

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

C3はコンパイルエラーになるので、実装のミスや、親が変更されたときに気付くことができます。

Typed Class Constants

賛成25反対0で受理。

クラス定数に型が書けるようになります。

enum E {
    const string TEST = "Test1";
}

trait T {
    const string TEST = E::TEST;
}

interface I {
    const string TEST = E::TEST;
}

class C implements I{
    use T;
    const string TEST = E::TEST;
}

わかりやすい!

mb_str_pad

賛成15反対1で受理。

マルチバイト対応版str_padです。

$str = 'あ';
var_dump(str_pad($str, 2, 'い')); // あ
var_dump(mb_str_pad($str, 2, 'い')); // あい

うっかり日本語にstr_padを使うと変な記号になったりpadできなかったりしたのですが、今後は思ったとおりのパディングになってくれます。

Dynamic class constant fetch

賛成15反対4で受理。

文字列からクラス定数を取得できるようになります。

$foo->$bar;    // OK
Foo::${$bar};  // OK
Foo::{$bar}(); // OK

Foo::{$bar};   // PHP8.3以降OK

他の項目はだいたい文字列でキーを指定する方法があるのですが、なぜかクラス定数にはそれができませんでした。
今後はクラス定数にもこの書き方ができるようになります。

また、この機能の導入に伴い、ENUMの使い勝手が大幅によくなりました。

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

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

べんり。

Arbitrary static variable initializers

賛成25反対0で受理。

static変数に変数や関数を突っ込めるようになります。

function foo(int|null $start){
    static $i = $start; // PHP8.3以降OK
    echo $i++;
}

foo(5); // 5
foo();  // 6

これまでstatic変数の初期化には固定値しか入れられなかったのですが、変数や関数が設定できるようになりました。
なお、特に技術的理由や意図的に固定値しか設定できないようにしていたわけではなく、なんとなく昔からそうなっていただけみたいです。

Readonly amendments

賛成26反対0で受理。

readonlyプロパティをcloneするときに再初期化できるようになりました。

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()
    {
        unset($this->baz); // __cloneから呼ばれている場合はOK
    }
}

$foo = new Foo(new DateTime(), new DateTime());
$foo2 = clone $foo;

マジックメソッド__clone()内において、readonlyプロパティを一回だけ変更できるようになります。

私には正直これのメリットがよくわからないのですが、全員賛成という投票結果からすると何らかの大きな利点があるのでしょう。

PDO driver specific sub-classes

賛成23反対0で受理。

PDOにドライバごとのサブクラスが追加されます。

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

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

// ファクトリーメソッド
$pdo = PDO::connect($dsn); // DSNがMysQLならPdoMySqlに、PostgresならPdoPgsqlになる

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

どうして今まで存在しなかったのだろうって思いますねこれ。

データベースに固有の機能を使えるようになります。
そのかわり移植性は下がりますが、どうせDB移植なんて滅多にやんないし利便性のほうが勝るでしょう。

もちろんこれまでのnew PDO()も使い続けることもできて、その場合はこれまでと一切変わりません。

Randomizer Additions

乱数の機能追加です。

$randomizer = new \Random\Randomizer();

// ランダムな文字列を生成する
$randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16);

// aが75%、bが25%のランダム文字列を生成する
$randomizer->getBytesFromString('aaab', 16);

// (-180, 180] の乱数を生成する
$randomizer->getFloat(-180, 180, \Random\IntervalBoundary::OpenClosed);

// [0, 1)の乱数を生成する
$randomizer->nextFloat();
  // $randomizer->getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen)と同じ

暗号学的に安全なランダム文字列や、範囲指定の乱数を簡単に作れるようになりました。
開区間閉区間も指定可能です。
これはたいへん使い勝手が良いですね。

json_validate function()

賛成18反対1で受理。

JSONが正しいかどうか判定できる関数が追加されます。

var_dump(json_validate('{ "": "": "" } }')); // false

json_decodeに突っ込めば正しいかわかるからいらんだろとの声も上がっていたのですが、丁寧な説得と様々なユースケースの提供によって、みごとに採択まで行きつきました。

Define proper semantics for range() function

賛成20反対0で受理。

range()関数の修正およびハンドリング機能強化です。

rangeにあまり一般的でない引数の渡し方をした場合、不自然な挙動になることがありました。
そのような場合もなるべく自然な動作になるようにし、また不正な引数にはエラーを出すようにします。

var_dump(range(0, 3, -1));
// PHP8.2まで [0, 1, 2, 3]
// PHP8.3以降 ValueError

var_dump(range('9', 'A'));
// PHP8.2まで [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
// PHP8.3以降 [9, :, ;, <, =, >, ?, @, A]

var_dump(range('', 0));
// PHP8.2まで [0]
// PHP8.3以降 [0]  Warning: range(): Argument #1 ($start) must not be empty, casted to 0

そもそもこれまで$stepの-1が場合によっては無視されていたってのが衝撃だな。

More Appropriate Date/Time Exceptions

賛成25反対1で受理。

DateTimeのエラーハンドリング機能強化です。

$i = DateInterval::createFromDateString('last Monday of');
$sub = (new DateTimeImmutable())->sub($i);

// PHP8.2  Warning: DateTimeImmutable::sub(): Only non-special relative time specifications are supported for subtraction
// PHP8.3  Fatal error: DateMalformedIntervalStringException 

何種類かのDateExceptionおよびDateErrorを導入し、細かなバリデーションを行うことができるようになります。

Improve unserialize() error handling

unserializeのエラーハンドリング機能強化です。
3つの提案があり、うち2つが受理されました。

unserialize('foo');
// PHP8.2  E_NOTICE
// PHP8.3  E_WARNING
// PHP9.0  UnserializationFailedException

unserialize('O:19:"SplDoublyLinkedList":3:{i:0;i:0;i:1;N;i:2;a:0:{}}');
// PHP8.2  UnexpectedValueException
// PHP9.0  UnserializationFailedException

unserializeしたとき、これまでE_NOTICEやE_WARNINGやUnexpectedValueExceptionなど様々なエラーや例外が発生していたせいでハンドリングしづらかったのですが、今後はUnserializationFailedExceptionで一括でcatchできるようになります。

なお、UnserializationFailedExceptionの導入自体はPHP9になりました。
PHP8.3ではそのための準備が行われるようです。

Make unserialize() emit a warning for trailing data

賛成20反対0で受理。

unserialize()の末尾に不要な文字列が入っていた場合に警告を出します。

var_dump(unserialize('i:5;i:6;'));

// PHP8.2  エラー出ない
// PHP8.3  unserialize(): Extra data starting at offset 4 of 8 bytes

i:5;i:6;のパースはi:5;で終わっていて、値はint(5)になります。
その後ろにあるi:6;は全く不要な文字であり、これまでは無視されていましたが、今後はE_WARNINGになります。

どうやらCVE-2023-21036と同じ脆弱性の問題があるとかなんとか。

Saner array_(sum|product)()

賛成27反対0で受理。

array_sum/array_productの改善です。

echo gmp_init("123") + 456;
// PHP8.2  579
// PHP8.3  579

echo array_sum([gmp_init("123"), 456]);
// PHP8.2  456 ←
// PHP8.3  579

array_sumarray_productは、これまで一部の動作が算術演算子と異なっていました。
PHP8.3以降は同じ動作になります。

Path to Saner Increment/Decrement operators

賛成25反対0で受理。

インクリメント・デクリメント演算子を改善します。


$i = 10;
var_dump($i-1); // 9
var_dump(--$i); // 9

$n = null;
var_dump($n-1); // -1
var_dump(--$n); // null

$s = 'foo';
var_dump($s-1); // Fatal error
var_dump(--$s); // "foo"
var_dump($s+1); // Fatal error
var_dump(++$s); // "fop"

intとfloat以外の型へのインクリメント・デクリメントは、算術演算子と異なる結果になることがあります。
また、時に想像と斜め上の挙動をすることがあります。
文字列にインクリメントすると次の文字になるけどデクリメントは効かないなんてところは特に意味がわかりませんね(これは移植元のPerlからそういう仕様)。

これを改善し、++$i$i+1と同じ挙動になるようにします。
intとfloat以外の型へのインクリメント・デクリメントはPHP8.3でDeprecatedとなり、PHP9で動かなくなります。

また文字列には、インクリメント・デクリメントを行う関数str_increment()str_decrement()を別途追加します。

$s = 'foo';
var_dump(str_decrement($s)); // "fon"
var_dump(str_increment($s)); // "fop"

これまでできなかった文字列デクリメントもできるようになるのはいいですね。

Use exceptions by default in SQLite3 extension

賛成21反対0で受理。

SQLite3拡張モジュールは、デフォルトでは例外を使わないようになっています。
しかしPDOMySQLiもとっくにデフォルト例外に移行しているため、SQLiteも例外をデフォルトにすることにします。

var_dump($sqlite3->enableExceptions());
// PHP8.2 false
// PHP8.3 false
// PHP9.0 true
// PHP10.0 Fatal error Undefined method

$sqlite3->enableExceptions(false);
// PHP8.2 OK
// PHP8.3 Deprecated
// PHP9.0 Fatal error

まずPHP8.3ではenableExceptions(false)するとE_DEPRECATEDが発生します。
ただし初期値はfalseのまま変わりません。

PHP9.0ではenableExceptions(false)することができなくなり、デフォルトもtrueになります。

最後にPHP10ではenableExceptions()メソッド自体がなくなります。

Deprecate remains of string evaluated code assertions

賛成24反対0で受理。

assert_options()関数、および関連する定数を削除します。

assert_options(ASSERT_BAIL, 1);

// PHP8.2 エラー出ない
// PHP8.3 Deprecated

昔はassert()の引数$assertionに文字列を渡すとevalされて評価されていたのですが、この使い方はPHP7.2で非推奨となりPHP8.0で削除されました。
しかし機能自体は削除されたものの、関連するini設定が一部残っていました。

またassert_options()は元よりini_set()を使えとドキュメントに書かれているくらいだったのですが、正式に非推奨となります。

Deprecate functions with overloaded signatures

標準関数におけるシグネチャのオーバーロードを廃止します。
なお、ここで言うオーバーロードはPHPのオーバーロードではなく、一般的な意味のオーバーロードです。
どうして同じ言葉にしてしまったのか。

例としてDatePeriod::__construct()はシグネチャが3種類もあります。

class DatePeriod
{
    public function __construct(DateTimeInterface $start, DateInterval $interval, DateTimeInterface $end, int $options = 0) {}
    public function __construct(DateTimeInterface $start, DateInterval $interval, int $recurrences, int $options = 0) {}
    public function __construct(string $isostr, int $options = 0) {}
}

このうち、一番上のシグネチャだけを残します。
その他のシグネチャはPHP8.3でDeprecateとし、PHP9で削除します。

PHP9
class DatePeriod
{
    public function __construct(DateTimeInterface $start, DateInterval $interval, DateTimeInterface|int $end, int $options = 0) {}
    public static function createFromISO8601String(string $specification, int $options = 0): static {}
}

削除されるシグネチャのうち、今後も必要そうなものには代替関数なりメソッドなりが用意されます。

他にもたくさんの関数やメソッドについての提案があり、それぞれ受理されたり却下されたりしています。

Deprecations for PHP 8.3

PHPには、主に歴史的な理由から、時代遅れになった機能、セキュリティ的に問題のある機能、そもそもなんでこんなものが存在するのか機能などが多々存在します。
ということでPHPではバージョンアップのたびにそれら負の遺産を消したり消さなかったりしています。
今回は以下の4機能が、PHP8.3で非推奨となり、PHP9で削除されます。

mb_strimwidth('abcde', 0, -1);
// PHP8.2 abcd
// PHP8.3 abcd Deprecated
// PHP9.0 Fatal error

var_dump(NumberFormatter::TYPE_CURRENCY);
// PHP8.2 値は取れる エラー出ない
// PHP8.3 値は取れる Deprecated
// PHP9.0 Fatal error Undefined constant

ldap_connect($host, $port);
// PHP8.2 エラー出ない
// PHP8.3 Deprecated
// PHP9.0 Fatal error

mb_strimwidth()の引数$widthに負の数を与えられなくなります。
幅に負数を与えると末尾からの数を数えるのは文字列関数に共通する挙動ですが、そもそもmb_strimwidth()は『文字列を特定長にする』目的で使われるものであり、目的と合わない機能です。
元の文字列長に依存する切り取りを行いたい場合はmb_substr()などを使いましょう。

定数NumberFormatter::TYPE_CURRENCYが削除されます。
この定数は、定義こそされているものの実装されていませんでした。

定数MT_RAND_PHPが削除されます。

かつてPHPの乱数には壊れたメルセンヌツイスター問題というものが存在しました。
これはPHP7.1で修正されたのですが、互換などの理由で壊れた乱数をあえて再現する手段として定数MT_RAND_PHPが導入されました。

その後PHP8で実は壊れたメルセンヌツイスターが壊れていることが発覚したのですが、どこからも文句が来なかったしもう互換いいんじゃねってなったみたいです。

ちなみに壊れていない方のメルセンヌツイスターも削除しようという提案もありましたが、さすがにこちらは却下されました。
とはいえメルセンヌツイスターは暗号論的に安全ではないので、今から新しく使うべきではありません。

ldap_connectのシグネチャのうち2引数のほうが削除されます。
今後は1引数のほうを使いましょう。

感想

PHP8.3は既にα版が提供されています。
Linux / Windows
締め切り間際で受理された機能などはまだ入ってないものもありますが、既に一部の機能は試すことができるので、遊んでみてはどうしょうか。

Nikitaの遺産が残っていたPHP8.2と異なり、PHP8.3は彼の手はほとんど入っていません。
しかしながらも着実に様々な改善、改良がなされており、次世代にしっかり引き継がれているのがわかりますね。

今後もPHPはeasyかつsimpleな言語であり続けてほしいものですね。

42
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
19