RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

PHPerのための「PHP8.2の新機能」を語り合う【PHP TechCafe イベントレポート】

弊社で毎月開催し、PHPエンジニアの間で好評いただいているPHP TechCafe。
2022年8月のイベントでは「PHP8.2の新機能」について語り合いました。
弊社のメンバーが事前にまとめてきたPHP8.2の新機能に関する情報にしたがって、他の参加者に意見を頂いて語り合いながら学びました。
今回はその内容についてレポートします。

PHP8.2 新機能について

PHP8.2の新機能は弊社のメンバーが事前にShowNoteにまとめています。 今回のイベントではこのノートに沿って新機能をみていきました。 hackmd.io

Deprecate dynamic properties

未定義のプロパティに値を代入した時の動作が変更され、動的プロパティが実質使えなくなります。

  • PHP8.1まで: プロパティが生成される
  • PHP8.2: Warningが発生するが、プロパティが生成される。
  • PHP9.0: 例外が発生する。

以下のコードを例では、Userというクラスで未定義のプロパティnane(nameのtypo)に値を代入しようとしているため、PHP8.2ではWarningが発生するようになります。

<?php
class User {
    public $name;
}
 
$user = new User;
 
// Assigns declared property User::$name.
$user->name = "foo";
 
// Oops, a typo:
$user->nane = "foo";
// PHP <= 8.1: Silently creates dynamic $user->nane property.
// PHP    8.2: Raises deprecation warning, still creates dynamic property.
// PHP    9.0: Throws Error exception.

この新機能については「タイポなどによって、意図しないプロパティが生成されることを防げるため、良い変更点」と紹介されましたが、その一方で「影響の大きい変更であるため、バージョンアップの対応が辛そう」というバージョンアップの大変さを嘆く弊社エンジニアの意見もありました。
また、「trait経由だと生やせる」「一回serializeしてunserializeするときに生やせる」など抜け穴が見つかっていることも話題に上がっていました。

「AllowDynamicProperties」アトリビュートによる救済措置

昔のプロジェクトでは、未定義のプロパティを意図的に生やすということを行うこともあったようです。 このようなレガシープロジェクトへの救済措置として#[AllowDynamicProperties]というアトリビュートを付ければとりあえず例外を回避できます。

<?php
#[AllowDynamicProperties]
 class User() {}

 $user = new User();
 $user->foo = 'bar';

参加者からはこの救済措置について、以下の意見があがっていました。

  • PHP8.0の時は影響の大きい変更がありましたが、そんなに救済措置がなく「用意すべき」という声があがっていたので、こういう救済措置を入れるのは優しいなと思う。
  • PHPを長くご愛好している人ほど辛い思いをするっていうのは本意ではないと思うので、ちょっとは考えてもらわないと往年のPHPプロジェクトは辛いと思う。
  • 結局これを色んなクラスにつけていく未来しか見えないので、中々の苦行が待っていそう

Readonly classes

PHP8.1では上書きできないプロパティであるReadonly propertyが追加されましたが、今回のPHP8.2ではReadonly classesが追加されます。
Read Propertyについては過去のPHP TechCafe「PHP8.1をもっと語り合う」でも触れられています。
※参考

tech-blog.rakus.co.jp

Readonly classesではクラスをreadonlyで宣言することによって、そのクラスのプロパティ全てがreadonly機能を持つことになります。
動的プロパティも当然できないため、先ほど話題に上がっていた#[AllowDynamicProperties]アトリビュートをつけてもエラーになります。
「Deprecate dynamic properties」についてはエンジニアの中で意見が割れていたようですが、この機能については反対意見があまりなかったようで、 イベント参加者からも「不変な値オブジェクトとして利用できるんじゃないかなと期待している。」といった前向きな意見がありました。

Constants in Traits

PHPには、コードを再利用するためのトレイトという仕組みがあります。
※参考
PHP: トレイト - Manual

PHP8.2ではこのトレイトに、定数を定義できるようになります。

従来のトレイトの問題点

これまではトレイトに定数を直接定義することができず、クラスかインターフェースに定義した定数を利用するしかありませんでした。
しかし、この2つの方法にはそれぞれ難点があります。

  1. クラスに定数を定義する場合
    トレイトを利用するための定数定義がトレイト自体によって提供されていない。

  2. インターフェースに定数を定義する場合
    インターフェース側で定義していることは実装上自然であるが、トレイトを利用するクラスがインターフェースを実装していないといけない。

以上の問題点もあったため、今回のようにトレイトで定数を定義する事ができるようになることで、モジュールとしてのトレイトの完全性が向上するということで提案されました。 参加者からも「本当に使いやすさだけが向上した素敵なアップデートだと思う」という喜びの声があがっていました。

Deprecate ${} string interpolation

文字列で変数展開する際の ${} 表記の挙動が非推奨になります。

<?php

$foo = "bar";
$bar = "foo";

var_dump("$foo");     // OK
var_dump("{$foo}");   // OK
var_dump("${foo}");   // 非推奨: 文字列での ${} の使用は非推奨
var_dump("${$foo}");  // 非推奨: 文字列での ${} (可変変数) の使用は非推奨

PHPのヒアドキュメントなどでも、変数の中身を出力する時に${}が使えましたが、PHP8.2からは非推奨になるようです。

${}表記が非推奨になる理由

${}表記が非推奨になる理由については、挙動がよく分からないというものがあります。
例えば、挙動が分かりにくくなるパターンとして以下のようなパターンが挙げられます。

パターン1: 配列

<?php

$foo = ['bar' => 'bar'];

var_dump("$foo[bar]");
var_dump("{$foo['bar']}");
var_dump("${foo['bar']}");

// すべて "bar" が出力されるが最後の挙動も "bar" になることが理解しづらい
// しかも、可変変数は利用できない

こちらの例では、$fooという変数の中にbarという連想配列がある状態です。
var_dump("$foo[bar]");var_dump("{$foo[‘bar’]}");というのは問題ないのですが、var_dump("${foo[‘bar’]}");とするとどういう挙動になるのか想像しにくくなります。
さらにこのパターンの場合、可変変数が利用できないという点でも問題になります。

パターン2: オブジェクトのプロパティ

<?php

$foo = (object)['bar' => 'bar'];
 
var_dump("$foo->bar");
var_dump("{$foo->bar}");

// このパターンでは ${} パターンが利用できない

オブジェクトのプロパティでは、そもそも${}の表記が使えないです。
こちらも${}の挙動の分かりにくさの一因になっています。

パターン3: メソッドコール

<?php

class Foo {
    public function bar() {
        return 'bar';
    }
}
 
$foo = new Foo();
var_dump("{$foo->bar()}");

// メソッドコールで許容されているのは上記のみ

メソッドコールでも{$foo}表記しか使えません。

以上のように一番複雑なのは${}というパターンであり、これがあると分かりにくいということからPHP8.2からは非推奨、PHP9.0からはエラーになるようです。

こちらも影響の大きい仕様変更に思えますが、「検出が簡単」「それぞれのパターンの直し方がRFCで解説されているので、それを参考に書き直したら問題ない」という観点から「バージョンアップ作業はそこまで大変ではないのでは?」という結論に落ち着いていました。
※参考: RFC Deprecate ${} string interpolation wiki.php.net

Deprecate partially supported callable

call_user_func($callable_func)では実行できるが、$callable_func()では実行できない以下のような構文が将来的に廃止されます。

<?php

"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

この機能についてもPHP8.2で非推奨になり、PHP9.0で廃止されます。
PHP8.1でFirst-class callable syntaxという機能が追加され、今回の一部のcallableの廃止はそれに付随しているようです。
※参考: RFC First-class callable syntax wiki.php.net

こちらの機能の廃止については 「変な呼ばれ方や変な構文をなくしていきたいって狙いですね。」「さっきの${}もそうですけど、統一されていないようなものはどんどん消していきましょうって事ですね。」と参加者の間で考察されていました。

MySQLi Execute Query

MySQLiに新しい関数mysqli_execute_queryが追加されます。

mysqli_execute_queryとは

下記3つの関数をまとめた関数です。

  1. mysqli_prepare()
  2. mysqli_execute()
  3. mysqli_stmt_get_result()

使い方

PHP8.1以前

<?php

$statement = $db->prepare('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)');
$statement->execute([$name, $type1, $type2]);
 
foreach ($statement->get_result() as $row) {
    print_r($row);
}

PHP8.2以降

<?php

foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
    print_r($row);
}

こちらの関数追加については投票結果が全会一致だったようです。
参加者からも以下のような賛成の声があがっていました。

  • 確かにめんどくさいなと思っていたので、「うん、確かに」としか言いようがなかった。
  • Prepairとか使ってきれいに書かないと安全性が担保できないくらいなら使いやすくしようと、実に正しい解決法ですね。

Fetch properties of enums in count expressions

PHP8.1で複数の定数をまとめるEnums型 が追加されました。
※参考
PHP: 列挙型 / Enum - Manual
しかし、PHP8.1のenumのcaseはオブジェクトである為、連想配列のキーに使うことができませんでした。
そこでPHP8.2では、->を使って定数式の中でenumのプロパティを取得できるようになりました。

<?php

enum A: string {
    case B = 'B';
    // 現在コレはダメ
    const C = [self::B->value => self::B];
}


enum E: string {
    case Foo = 'foo';
}
 
// 'Foo'が取れる
const C = E::Foo->name;

ライブラリの作者さんが安全なコードを書くのにほしいという需要があったようですが、「いまいち使いどころが分からない」という意見もありました。

Allow null and false as stand-alone types

元々UNION型でのみ、利用可能だったFalse型Null型が型宣言が許される位置全てにおいて利用可能になります。
関数がエラーであることを示すために、返り値としてfalseを返す場合があります。
これを表すためにFalse型というものが作られましたが、この型がどこでも使えるようになります。
この変更により「静的解析で便利になるのでは?」という期待の声があがっているようです。

Add true type

先ほど紹介したNull型とFalse型が入った後、じゃあTrue型もあっていいのでは? という流れからTrue型が追加されることになりました。
参加者からは「True型ってそんなに使うことあるんですか?」という声もあがりましたが、別の参加者からRFCに紹介されているサンプルコードをもとにユースケースが説明されました。

True型のサンプルコード(PHP: rfc:true-typeより抜粋)

<?php

class User {
    function isAdmin(): bool
}
 
class Admin extends User
{
    function isAdmin(): true
    {
        return true;
    }
}

UserクラスのisAdmin()の返り値はboolで型指定されていますが、Userクラスを継承したAdminクラスは必ずtrueを返す仕様になっています。
このような場合に返り値にTrue型を指定することで、静的解析でより有用なチェックができるようになるというケースがあるようです。
この説明を受けるとTrue型の有用性について疑問を抱いていた参加者も納得している様子でした。

Random Extension 5.x

PHPで乱数を使用する関数の実装に問題があることが以前から指摘されていました。
具体的には

  • メルセンヌ・ツイスター(疑似乱数ジェネレーター)の実装が壊れている
  • メルセンヌ・ツイスターの状態は PHP のグローバル領域に暗黙的に格納される。そのため外部ライブラリで乱数が乱れたりといった問題が発生する。
  • shuffle(), str_shuffle(), array_rand(),random_int()といった組み込み関数ではメルセンヌ・ツイスターが乱数ソースとして使用されるため、暗号的に安全な乱数が必要な場合は危険。
  • PHP での乱数の実装は、歴史的な理由から標準モジュール内に散らばっている。

などが挙げられます。
そのため、まざまなランダム化メソッドを提供する単一のクラス、Random\Randomizerクラスが追加されます。
このRFCが出された理由は、RFCを出された方のスライドを見ると全て分かると以下のスライドが紹介されていました。
speakerdeck.com こちらのスライドに目を通した参加者からは以下のような意見があがっていました。

  • 外部の影響を受けて乱数の値が変わるのは、確かに乱数とは言えない。
  • そもそも乱数生成のPOSTが高かったみたいですね。なのできちんとライブラリを使って速度も早く安定したRandomを作りたいと。素晴らしい話だと思います。

Disjunctive Normal Form Types

プロパティやパラメータに対して、論理式の形式で複雑な型指定をすることができるようになります。
RFCではA | (B&D) | (B&W) | nullといった細かい例もあり、かなり細かい型指定ができるようになるようです。
ゲームのパラメータなどで、クラスに対して型を定義し、これとこれは受け取るけどこれは受け取らない、というように細かい制御を行うことがあるという事例が紹介されていましたが、 参加者からは「PHPでそこまで型を使いこなすようなことをやるのか?」「何かに使えそうな気はするけどパッと例は出てこない」など疑問の声もあがっていました。

その他のRFC

上記で紹介したRFC以外にも、「PHP TechCafe」では以下の新機能や変更点について触れられていました。

Remove support for libmysql from mysqli

MySQLのモジュールはmysqlndとlibmysqlの2種類が存在するため、このうちlibmysqlを削除するという内容です。
PHP5.4以降はmysqlndがデフォルトとなっており、libmysqlを選ぶ利点がほぼないことから全会一致でlibmysqlの削除が決まったようです。

Make the iterator _*() family accept all iterables

foreachに渡せる反復可能なオブジェクトであるiteratorですが、PHP8.2ではiterator_to_array()とiterator_count()にiterable型の変数を渡せるようなります。

Redacting parameters in back traces

スタックトレースに引数を出力しないようにするアトリビュート#[\SensitiveParameter]が追加されます。 参加者からは「DBの接続情報などが画面に表示されることによる情報流出を防ぐことができるのでは?」という期待の声があがっていました。

Deprecate and remove utf8_decode() and utf8_encode()

utf8_decode()とutf8_encode()が削除されます。
この関数は、任意の文字列をUTF-8エンコードデコードできるかと思いきや、変換相手はLatin 1固定であり、誤解を招く関数名であることから削除されたようです。

Locale-independent case conversion

ロケールの影響を受けていたstrtolowerなどの関数が、PHP8.2からロケールの影響を受けないようになります。 そもそも従来のstrtolowerなどの関数がロケールの影響を受けることに対して驚きの声があがっていました。

まとめ

今回はPHP8.2の新機能について、イベント参加者の生の声を交えてまとめてみましたがいかがでしたでしょうか? イベントでは追加される新機能の内容だけでなく、採用が決まるまでの裏話なども語られており、有意義なTech Cafeであったと思います。

PHP8.3についても導入される機能が続々と決まってきているので今後もどのような機能がPHPに追加されるのか注目していきたいです!

PHP TechCafe」では今後もPHPに関する様々なテーマのイベントを企画していきます。 皆さまのご参加をお待ちしております。


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.