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

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

PHP8.1 の新機能について語り合う・後編【PHP TechCafe イベントレポート】

f:id:tech-rakus:20210927173913p:plain 弊社で毎月開催し、PHPエンジニアの間で好評いただいているPHP TechCafe。2021年8月のイベントでは社外でご活躍されているPHPエンジニアにもご参加いただいて「PHP8.1の新機能」について語り合いました。

rakus.connpass.com

PHP8.1の新機能は8.0に比べれば少ないとはいえ、順番に追いかけてみると思ったより大きなボリュームになったためイベント内容を2回に分けてレポートします。今回は後編として後半の半分をご紹介します。

前編はこちら。

tech-blog.rakus.co.jp


PHP8.1 新機能について

PHP8.1の新機能は弊社のメンバーが事前にShowNoteにまとめています。今回はこの後半部分の新機能をみていきました。

hackmd.io

Pure intersection Types

交差型 と呼ばれ、型AとBがあるときに、AでありかつBの(両方の性質をもつ)型を表すときに A&B のように記述します。 「AまたはB」のような表現は8.0で Union Type という型が導入されており、 縦棒で A|B のように記述します。 「それの逆版が8.1で入ります。」と紹介されました。

※参考:PHP8.0で導入されたUnion Type

wiki.php.net

この交差型に対しては次のような意見が出ました。

  • 新しく型を定義しなくても良くなる
  • インターフェース名から両方の特性を持っているっていうのがひと目でわかるので、意図が分かりやすくなるし、管理もしやすくなるのかな?
  • 色々と賛否や考え方が出るかもしれません

交差型のサンプルコード( ShowNote より抜粋)

<?php

class A {
    private Traversable&Countable $countableIterator;
 
    public function setIterator(Traversable&Countable $countableIterator): void {
        $this->countableIterator = $countableIterator;
    }
 
    public function getIterator(): Traversable&Countable {
        return $this->countableIterator;
    }
}

※参考:サンプルコードで使われている TraversableCountable インターフェースについて

www.php.net

www.php.net

上記のコードを元に詳しく解説されました。今までは TraversableCountable という2つの振る舞いを持つオブジェクトを作るようなケースでは両方を継承する新しいインターフェースを作る必要がありました。バリエーションが増えてくると、この振る舞いとこの振る舞いとこの振る舞いの組み合わせで…というように、どんどん複雑なインターフェイスが増えてきます。交差型を使うことで新しく型を定義しなくても良くなり、上記のように TraversableCountable の両方を持っているというのがひと目でわかりやすくなります。

ただし、使う上では注意も必要です。

  • ループできる Traversable とカウントできる Countable はセットになりがちと思えますが、全然関係ないAとBという属性が Traversable でくっつけると密結合になるのかなと思うので、気をつけないと変な設計になる。
  • 実は嬉しいところがあんまりない。

あまり使いすぎると複雑な設計になるので注意が必要です。交差型が必要になるようなシーンでは以下のような疑問を持ってみるのも良さそうです。

  • そもそもここまで細分化していることがおかしいのでは
  • 連想できない無関係なものが一つのオブジェクトにまとまっているのがうまくドメインを分解できていないって事なのでは

他の言語ではあまりない機能

また、PHP以外のプログラミング言語にも目を向けた議論がありました。

  • 普通の静的型付言語でもあまりない機能。
  • どちらかといえば動的型付言語に拡張で導入される例が多い気がします。
  • 多重継承が前提の機能なのでいきなり使うようなものじゃないですが、使えると便利。
  • 正直ここまでやるならHaskell系の言語やScalaとか、多重継承が普通にできる設計かつ強い静的型付の言語を使うほうが良い気もしますが、TypeScriptでも導入されているので動的型付言語ならではの使いどころがあるのではないでしょうか?

「便利だけど、あまり使うシーンはない?」という、やや消極的な意見が多かったものの、RFCをみてみると賛成意見がほとんどで採択されていました。

f:id:radiocat:20210923110004p:plain
RFCでは賛成30で採択

交差型は「もともとPhpStormやPHPDocでサポートされていたもの」とのことで、交差型が導入されることで何かを壊すということもなく、「表現の幅が広がる」ということから受け入れられやすかったのではないか?とのことでした。

※参考:PHPStanの作者のブログ(PHP8.1の機能ではできないが多重継承的なことができる事例も紹介されている)

medium.com

New never type

常に例外出したり、exitで終わってしまうような処理の返り値に never と書いて表せるようになります。「レガシーなコードであればあり得る話だよなあと思うので、かなり使い所が多いんじゃないか」とのことです。

<?php

function redirect(string $uri): never {
    header('Location: ' . $uri);
    exit();
}

RFC段階では nevernoreturn の二案があったそうですが、最終的に never になりました。参加者からも「 noreturn のほうがわかりやすい気もするんですけどね。」という意見がありました。「悪くなかったと思うんですけど、二語になるからややこしいって話があった」「(noとreturnの)間にハイフン入れるのか、アンダーバー入れるのか?ってなって一語にしようって話になったようです。」とのことです。「静的解析などをするうえでは never は別の目的で使いたいので noreturn を推していたみたいです」という背景もあったようです。

静的解析などで役立つ期待あり

上記のサンプルコードのように、リダイレクトしたり exit() で終わってしまうときに書いておけば「静的解析とかでもうまく使えそうな気がしますね。」とのことです。PhpStormで「 redirect を呼んだ次の行とかに違う処理があると実行されないステートメントという警告が出せる。」という実現イメージです。

最終的に never が採用されましたが、静的解析に関しては役立つ期待が持てそうです。「個人的にはReadonlyに並んで期待している機能ですね。」とのことでした。

New array_is_list function

配列のキーが0番目からスタートする配列なのか連想配列なのかを判定する機能です。 array_is_list という関数を使うことで、0番目から始まる配列なら true 、それ以外は false が返ります。

<?php

$list = ["a", "b", "c"];
array_is_list($list); // true

$notAList = [1 => "a", 2 => "b", 3 => "c"];
array_is_list($notAList); // false

参加者から質問がありましたが、 [0=>"A", 2=>"B", 3=>"C"] という連想配列でも false が返るようです。

ネーミングに関する議論

array_is_list というネーミングに関しては様々な議論があったようです。RFCの議論を読むと is_list という候補もあったことが書かれています。

wiki.php.net

参加者からも「名前がすごいですよね。」「何だろう?同じことを言っている感じがする。」「名前を見れば見るほどモヤモヤしますね。」という声があがっていました。

ここで議論しているものは「コンピュータサイエンスでいう連結Listとは違う」「これまでList構造というのは分割代入のために使われていた」が、そうではなく「純粋な配列」とのことです。「静的解析などではlistという型が使えるようになっていて連番で表す」ということで、そういう経緯もあってこういう名前になったのではないか?、「奇跡的なバランスでできた名前ですね。」と解説されました。

Final class constants

継承したclassの定数をオーバーライドすることを禁止することが出来るようになります。これまではclass間で定数を書き換えることができたため、親classの設計者の意図に反して「一定ではない」状態が起こりうるようになっていました。

<?php

class Foo
{
    final public const X = "foo";
}
 
class Bar extends Foo
{
    public const X = "bar";
}
 
// Fatal error: Bar::X cannot override final constant Foo::X

前編の「 Readonly Properties 」で final の使い方との違いが紹介されましたが、「 Readonly とはまた使い所が違う」とのことです。

New fsync function

ファイルの書き込み内容をメモリから実際にファイルへ書き込む機能です。 これは内部実装が変わって、「細かく言うとI/Oの順番が変わった」ことで、「途中で処理が止まっても書き込みをちゃんとしてくれる」とのことです。

エラーで落ちても書き込みは保証

ここでは「趣味でPHPでDBを作っている」という参加者から利用シーンの一例が紹介されました。

  • キャッシュメモリからディスクに書き込むところでエラーハンドリングとかで fsync が使えたらな、と思っていた
  • そういう用途でしかあまり使わない

たしかに用途は少なそうですが、使い方がイメージできて他の参加者も納得していました。

Explisit octal integerliteral notation

0o (ゼロ・オー)という接頭詞を置くことで8進数と明示できるようになりました。 元々、 0で始まる場合は8進数として扱われ、例えば 16 == 016016 は8進数と解釈されるためfalseになります。0o が使えるようになり視覚的にわかりやすく表記できます。

利用シーンは少ないが事故を防げそう

8進数を使う場所は限られるのでいつ使うのだろう?という疑問もありますが、一例として chmod の利用シーンが紹介されました。 chmod でファイルのパーミッションを設定するとき 0755 と書くと正しくパーミッションを設定できますが、間違えて 755 と書くと意図しない動作になります。

「誰かが 07550 が邪魔じゃない?と思って消したら事故になる、というケースを防げるんじゃないかと思います。」と、0o と明記することで事故が防ぎやすくなるメリットの一例が紹介されました。

※参考: chmod 関数

PHP: chmod - Manual

また、他にも「PHPのバージョンアップしようとした時に、8進数や16進数の振る舞いが変わりますとなった時にどうやって探せば良いんだと思ったんですが、接頭詞を置けば探しやすくなりますね。」という意見もありました。

そのほか、なぜ 0o という見間違えやすい文字を連続して使うのか?という意見もありましたが、8進数=octaという意味から来ていることが解説されました。16進数はHexの x を使って 0x です。このあたりはPHP以外の言語でも採用されている表記法でもあります。

Restrict $GLOBALS usage

ここからは仕様変更の機能についての紹介です。スーパーグローバルの$GLOBALS で他の変数にアクセスできる特殊な変数ですが、一部の使い方ができなくなります。

<?php

$a  =  1 ; 
$GLOBALS ['a']  =  2 ; 
var_dump$a);  // int(2)

// Generates compile-time error:
$GLOBALS = [];
$GLOBALS += [];
$GLOBALS =& $x;
$x =& $GLOBALS;
unset($GLOBALS);

パフォーマンス向上のための使用制限

なぜこのような変更が行われたのかについて、次のように解説されました。

  • 他の変数にはない特殊な使い方が出来るようにしていたことで、PHP全体の配列アクセスへのボトルネックになっていた
  • 見るからに誰も使わなさそうな処理がPHPユーザー全体の処理の足かせになっていた
  • 世に転がっているライブラリを覗くとそんな使い方しているやつほとんど無いやとなった
  • じゃあ消しちゃおうということで使えなくしようとなった

PHP全体の配列アクセスのパフォーマンス向上のため、「元々はできていたんだけど今後はできなくなります。」とのことです。誰も使わなそうではありますが、「ある種便利な機能だったと思うんですよね。 $GLOBALS 経由でなんでもアクセスできましたし。テストで変数全部吹っ飛ばすとか。」「無理やりいじるとかはあるのかなと。WordPressのカスタマイズ案件とかで、ベース部分はさわれないけどどうにかこうにかしたいみたいなケースとか。」という意見もあり、使わないだろうと決めつけるのも危険かもしれません。

なお、「 PHP8.1でいきなりエラーになります 」とのことで、非推奨化による警告などではない点も注意が必要です。

なお、 $GLOBALS の使用については上記のような特殊なケースで使用することはあるかもしれませんが、本来は「これ自体使うべきではないですね。」という意見も出ていました。今回の変更の影響にかかわらず、 なるべく $GLOBALS を使わず別の方法を検討するほうが良さそうです。

Deprecate passing null to non-nullable arguments of internal functions

str_contents という標準関数の第2引数に null を指定することが非推奨化されます。

<?php

// PHP8.1 では非推奨、PHP9 では警告が出力される
str_contains("string", null);

str_contents はPHP8.0から導入された標準関数で、第1引数の文字列中に第2引数の文字列が存在する場合にtrueが返されます。

PHP: str_contains - Manual

PHP8.0では第2引数にnullを指定するとtrueが返されていましたが、これ自体が不具合だったようです。「PHPのユーザー定義関数とかだと普通はStringって型宣言しているので通らないんですが、PHPのネイティブな関数だからこそ入っちゃったバグですね。」とのことです。内部的にnullをキャストしたことで空文字列として処理され、「空文字列はあらゆる文字列の部分集合なのでtrueになる」ようです。

nullが入りうるパターンに要注意

str_contents に対して直接的にnullを指定するケースはあまりなさそうですが、参加者からは以下のような注意点も共有されました。

  • 未定義変数とかがnullが入りうるパターンなので要注意ですね
  • (PHP8.0から導入されたので)そんなに使っていないと思いますが、便利な関数なので発生しうるかな?
  • だから(PHP8.1で)一回警告を挟むんでしょうね

PHP8.0で早速使い始めたけど、特定のパターンで未定義変数が紛れ込むことはありそうです。万が一そのようなパターンがあってもPHP8.1の間は警告が出るので見つけたらすぐに対処しておくと良さそうです。

その他の変更点

時間の関係ですべては紹介できませんでしたが、これ以外の変更点も以下にまとめられているのでご確認ください。また、機会があれば次回以降のPHPTechCafeで話題にあがるかもしれません。

hackmd.io

今後はPHPUnitやLaravelをテーマに開催予定

次回以降は9月と10月のテーマと開催日が既に決まっており参加者を募集中です。参加頂くとブログには掲載しきれない細かい情報や、PHPの最新動向のニュースの紹介などもありますのでぜひご参加ください。

rakus.connpass.com

rakus.connpass.com


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

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

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

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