こんにちは。 SRE の工藤です。
普段は SRE としての活動の他、技術基盤本部として共通基盤やライブラリの開発、保守を行っていますが、コロプラにおける PHP バージョンアッププロジェクトが始まった時から関わっているということもあり、今でも多くのタイトルで対応を行っています。
今回は "コロプラにおける PHP のバージョンアップ" に焦点を絞り、PHPのバージョンサポート事情、運用しているゲームタイトルの対応をどのように行っているのか、その時に利用している "三種の神器" とも呼べるツール・ライブラリなどを紹介いたします。
PHP のサポート期間
PHP は原則、最新からさかのぼって 3 バージョンまでをサポート対象としています。アクティブサポート期間とセキュリティサポート期間の 2 種類があり、アクティブサポート期間内であればバグと脆弱性の修正、セキュリティサポート期間であれば脆弱性のみの修正が行われます。
PHP の公式サイトには現在のサポート状況がわかりやすい図で示されています。
補足として、エンタープライズ向けの PHP 製品を提供している Zend by Perforce (旧称: Zend Technologies) は PHP の延長サポートを有料で提供しており、 2022 年 10 月現在、PHP 5.6 についてもサポートが続けられています。
また、 Red Hat Enterprise Linux でも OS のリリースに対応するバージョンの PHP について独自の継続的なサポートがされており、 Red Hat 社員でもあり PHP のオフィシャルメンテナでもある Remi Collet 氏が継続的に保守を行っています。
https://centos.pkgs.org/7/centos-x86_64/php-5.4.16-48.el7.x86_64.rpm.html
PHP のバージョンアップと BC Break
PHP は言語として常に改善が行われ続けており、基本的に新しいバージョンであればあるほど多機能かつ高速な言語として進化しています。
一方で PHP には多くの言語としての問題も残されており、それらについて改善や修正が行われることで、以前のバージョンで動作していたコードが意図通りに動作しなくなること (下位互換性破壊: Backwards Compatibility Break 以下: BC Break) が発生します。 PHP の最新バージョンリリース時に公開される "下位互換性のない変更点" ページには、そのバージョンで行われた BC Break な変更一覧が記載されています。
もちろんこのような変更は PHP に限った話ではなく、多くのプログラミング言語で適宜行われるものであり、言語をより良くしていくためには必要不可欠なものです。
しかし、 PHP には様々な歴史的経緯により直感的でなかったり一貫性のない挙動が多く、それを修正するために大規模な BC Break が発生することもしばしば見受けられます。例えば、 PHP 8.0 では比較演算子に挙動の変更が行われ、大規模な BC Break が発生しました。
変更提案の RFC:
もともと直感的ではない挙動だったとはいえ、この変更に対して既存のコードが安全かを静的に検査するのは極めて難しく、テストコードが網羅できていないシステムを安全にバージョンアップするのは困難です。
とはいえ、前述の通り PHP は最新の 3 バージョンしかサポートが提供されておらず、 PHP 7.4 も 2022-11-28 にはサポートが終了してしまいます。どうしよう...
三種の神器
コロプラにおける PHP バージョンアップの歴史は長く、 2017 年末頃から継続的に活動を行っています。 2020 年頃のバージョンアップ事情についてはコロプラのオウンドメディアである Be-ars に記事を書いています。
今回の PHP 8.0 へのバージョンアップも、基本的には以前と同様の手法を元に行っていくこととしました。ただし、当時とは PHP コミュニティの状況も変化してきており、何より当時に比べて静的解析の分野が大幅に発達しています。そこで以前からあるコロプラ内製の互換拡張に加え、新たに二つのオープンソースのツールを活用することで PHP バージョンアップ三種の神器とすることとしました。
神器1: colopl_bc Extension / Library
先程の Be-ars の記事でも言及しているコロプラ内製の PHP Extension / Library です。互換性の失われた挙動を再現するためのもので、Extension は C 言語、 Library は PHP で書かれており、それぞれ Extension は実際の互換処理、 Library は実行されている PHP バージョンに応じて適切な振る舞いをする抽象化レイヤの役割をしています。
前章でチラッと紹介したコロプラのオウンドメディア Be-ars の記事で紹介されているもの (当時は colopl_php70bc と BCLayer という名前でした) の後継にあたります。
colopl_bc は以下のような機能を持っています。
- マイクロ秒のない DateTime, DateTimeImmutable インスタンス生成関数
- GNU libc 互換の乱数生成関数
- PHP の誤ったメルセンヌ・ツイスタ実装の乱数生成関数
- 各種曖昧比較演算子と同一の結果を返す関数
- 曖昧比較挙動と同一の結果を返す関数
- 以前のソート関数 (unstable sort) と同一の結果を返す関数
- 実行されている PHP のバージョンに応じて適切な振る舞いをする抽象化レイヤ(Library)
また、設定に応じて非互換が検出された場合に E_NOTICE レベルのエラーを発生させる機能があるため、そもそも本当に colopl_bc が必要なのかチェックしたり、先に移行作業を完了させ、徐々に colopl_bc への依存をなくしていくことが可能です。
一見便利に思えるこの拡張ですが、本来はこのようなことをすべきではありません。この手法を取ったのは、これ以外に互換性を完全に担保する方法がなかったためです。実際に可能な限り利用しないようにし、使っている箇所は安全性が担保でき次第拡張を利用しないようにする方針にしています。
C 言語で実装されたコードはメモリ等のリソースの管理を手動で行わなければならず、万が一メモリリーク等が発生した場合最悪プロセスがクラッシュする可能性があります。 colopl_bc では PHP 本体の手法に基づき Valgrind や LLVM Santizer 等の解析ツールを利用して安全性の検査を行っていますが、それでもリスクがゼロということにはならず、安易にこのような拡張を利用すべきではないでしょう。
神器2: PHPStan
最近 PhpStorm も公式にサポートを開始し、すっかり有名になった感のあるオープンソースの静的解析ツールです。
本来互換性のチェックを行うようなツールではありませんが、内部的に nikic/PHP-Parser を利用していることもあり、 PHPCompatibility よりも自前のルールを書きやすいという点から採用することにしました。
こんな感じでサクッと new \DateTime() や new \DateTimeImmutable() の使用を禁止するルールが書けてしまいます。素敵!
神器3: Rector
最近 PhpStorm でもサポートされるようになった Rector は、静的解析の力を利用してコードを自動的にリファクタリングしてしまうという静的解析の極地のようなツールです。
今まで PHP バージョンアップの際には互換性のないコードを手動で修正しており、対応漏れがないかのチェックや置換するための温かみのあるシェルスクリプトを都度作成するなど、膨大な作業が発生していました。
以下は Rector を用いて PHP 8.0 で互換性がなくなった曖昧比較演算子を互換関数に変換する例です。変換は静的解析を用いて構文を解釈した上で行われるので、対応漏れが発生しにくく、一度ルールを作成すれば様々なタイトルで流用できます。
おわりに
上記のように、コロプラでは "三種の神器" を用いて運用中タイトルの PHP バージョンアップへの追従をスムーズに行える体制を整えています。中でも PHPStan と Rector の存在は大きく、平行して新機能の開発が行われる状況下でも迅速かつ確実に対応が行える環境を整えることができました。
一方で PHP Extension を作るという選択をしたために Extension の PHP コアへの追従作業コストや保守するために PHP コアをある程度理解しておく必要が生じるなど、新たな問題も発生しました。このような問題については ADR (Architecture Decision Report) を残したり、マニュアルを読みやすく改善していくなど継続的な取り組みを行っていき、将来的には colopl_bc 自体を廃止したいと考えています。