Slide 1

Slide 1 text

#phperkaigi ©2021 RAKUS Co., Ltd. 今だから話せるPHP8バージョンアップの裏側 ~全5サービスの事例紹介~ 2022/04/10 PHPerKaigi 2022 久山勝生

Slide 2

Slide 2 text

#phperkaigi 自己紹介 ● 久山勝生 ○ 株式会社ラクス ○ 開発・運用・保守 ● 好きなもの ○ マネケンのワッフル @MasaKu_e MasaKuuuu 2

Slide 3

Slide 3 text

#phperkaigi 弊社のサービス ● PHPで作成されたアプリケーションが6つ ○ そのうち4つは10年以上の歴史を持つサービス ○ PHP8 バージョンアップ前はいずれのサービスも PHP7.3系 で稼働 3

Slide 4

Slide 4 text

#phperkaigi 弊社のEOL対応のポリシー ● 2年に1回はメジャーバージョンアップを実施 ○ サービスの品質担保 ○ セキュリティ対策 PHPバージョン セキュリティサポート 7.3 2021/12/6 7.4 2022/11/28 8.0 2023/11/26 8.1 2024/11/25 各サービス PHP8系への アップデートを検討 4

Slide 5

Slide 5 text

#phperkaigi PHP7.3 と PHP8 の差分

Slide 6

Slide 6 text

#phperkaigi PHP7.4 の新機能 ● 型付きプロパティ ● Preload ○ opcache に事前ロードするス クリプトを指定可能 class User { public int $id; public string $name; } ● アロー関数 $y = 1; // PHP7.4 で動作 $fn1 = fn($x) => $x + $y; // PHP7.4 以前で上記と等価 $fn2 = function ($x) use ($y) { return $x + $y; }; 6

Slide 7

Slide 7 text

#phperkaigi PHP8.0 の新機能 ● 名前付き引数 ● コンストラクタの プロパティ昇格 myFunction(paramName: $value); array_foobar(array: $value); ● UNION型 ● match式 class Point { public function __construct( public float $x = 0.0, public float $y = 0.0, public float $z = 0.0, ) {} } class Number { public function __construct( private int|float $number ) {} } echo match (8.0) { '8.0' => "マッチしない", 8.0 => "マッチする", }; 7

Slide 8

Slide 8 text

#phperkaigi バージョンアップによる影響 ● 以下を正しく理解して対応していく必要がある ○ 新機能 ○ 推奨されなくなる機能 ○ 下位互換性のない変更点 PHP8へのバージョンアップで注意すべきは 下位互換性のない変更点 8

Slide 9

Slide 9 text

#phperkaigi 下位互換性のない変更点 ● ChangeLog で確認できた件数 ○ PHP8 のChangeLogの件数が多い ■ レガシーコードにおいてインパクトが大きい変更も含まれる PHPバージョン ChangeLogの件数 7.4 27 8.0 166 インパクトの大きい変更 ① == による緩やかな比較の挙動変更 ② 新たなエラー(ValueError)の追加 ③ 標準関数の引数の型が厳密化 9

Slide 10

Slide 10 text

#phperkaigi == による緩やかな比較の挙動変更 ● 数値と文字列との比較に関する挙動が変更 ● 意図せず空文字が挿入されているパターンが数多く存在 ○ 空文字で初期化した後、値が設定されないパターンなど ■ すべての影響箇所を特定するのは難しい サンプル PHP8以前 PHP8 0 == “” TRUE FALSE 0 == “php” TRUE FALSE 42 == “42foo” TRUE FALSE 10 要注意

Slide 11

Slide 11 text

#phperkaigi 緩やかな比較を行う処理への影響 ● 演算子 ○ ==、!=、>、>=、<、<=、<=> ● 関数 ○ in_array()、array_search()、array_keys() ■ strict=true でない場合 ● ソート ○ sort()、rsort()、asort()、arsort()、array_multisort() ■ SORT_REGULAR を渡した場合 ● switch文 11

Slide 12

Slide 12 text

#phperkaigi 新たなエラー(ValueError)の追加 ● 引数の型は正しくても値が正しくない場合に発生 ○ パターン ■ 関数は正の整数を期待しているのに負の値を渡す ■ 空でない文字列/配列を期待しているのに空の値を渡す ● 以下は PHP8以前では Warning が発生 12 $randNum = array_rand([1,2,3], 0); $string = strpos("", "small",1);

Slide 13

Slide 13 text

#phperkaigi 標準関数の引数の型が厳密化 ● 配列関数の引数に配列以外の型を指定 ● 配列以外の型が指定されている引数に配列を与える ● BCMath関数の引数に数値形式以外を指定 ○ 以下は PHP8以前では Warning が発生 13 $num = 1; echo count($num); echo htmlspecialchars(["php"]); $a = '1.234'; $b = '5a'; echo bcadd($a, $b);

Slide 14

Slide 14 text

#phperkaigi PHP8バージョンアップ対応 ● 体制 ● 情報収集/共有 ● 影響調査 ● プログラム修正 ● テスト ● リリース後の不具合対応 14

Slide 15

Slide 15 text

#phperkaigi リリースまで 15

Slide 16

Slide 16 text

#phperkaigi PHP8バージョンアップ対応の体制 ● 各サービスからPHP8 バージョンアップの代表者を選出 ○ 各サービスは10名ほどのメンバーで構成 ○ その中から各チーム 1,2 名を代表者として選出 16

Slide 17

Slide 17 text

#phperkaigi 情報収集 ● PHP8 対応を一番最初に取り組むチームの代表者が情報収集 ○ 収集した情報は後から対応を開始する各チームの代表者に共有 ● 新しく見つかった情報などは社内チャットで共有 先行した情報収集 ・マイグレーションガイド ・チェンジログ ・セッション関連情報 ・php.ini 設定 17

Slide 18

Slide 18 text

#phperkaigi 影響調査 ● 調査結果を元に各サービス毎に以下の内容を調査 ○ フレームワーク/ライブラリ/CIツール ○ プロダクトのコード ■ チェック関数を利用して以前までと挙動に差異が生まれないか確認 ○ 本番環境のログ ■ PHP8 以降で Error になるログの洗い出し 18

Slide 19

Slide 19 text

#phperkaigi プログラム修正 ● 影響ありとなった箇所の修正 ○ Error になる箇所 ■ クラス名と同じ名前のメソッドを __construct()に置き換え ■ ラップ関数で置き換え, など ○ Warning になる箇所 ■ 以下の値が未定義の状態で呼び出されている箇所の修正 ● 変数 ● 配列のキー 19

Slide 20

Slide 20 text

#phperkaigi ● 歴史の浅いサービス ○ Laravel が利用されており仕組みを工夫して対応 ■ データの入出力箇所で型をチェックするようにする ● Laravel の Requestクラスのバリデーションルールを元に 自動的にキャストする仕組などを導入 ● 歴史の長いサービス ○ レガシーコードが混在し複雑化していて仕組みでは対応できない ■ 曖昧さを寛容に受け入れていた時代のPHPでバリバリ活躍 ● しらみつぶしな力技で対応 各サービスの特徴を考慮した修正 20

Slide 21

Slide 21 text

#phperkaigi テスト実施戦略 ● 修正が必要なことが明らかな箇所 ○ 事前調査で修正した箇所 ■ 単体テスト ● 修正が必要だが調査しきれていない箇所 ○ 事前調査で修正が漏れている可能性のある箇所の洗い出し ■ 全機能テスト ○ 顧客の重要業務において不具合が発生していないかの確認 ■ 重要機能テスト 21

Slide 22

Slide 22 text

#phperkaigi ● 修正箇所の単体テスト ○ 影響調査により明確に影響ありと判断できる箇所は単体テストで対応 ■ ユニットテスト ■ 手動テスト 単体テスト 22

Slide 23

Slide 23 text

#phperkaigi 全機能テスト ● プロダクトの全機能の動作を確認するためのテスト ○ 事前調査では拾いきれなかった不具合の洗い出し ■ 普段利用しない機能で不具合が発生していないかを確認 ● 対象機能の通常パターンを中心にテスト ● 異常系などのエッジケースなどは確認しない ○ PHPだけでなく他のMWアップデート等に備えて準備している 23

Slide 24

Slide 24 text

#phperkaigi 重要機能テスト ● 顧客の重要業務を遂行するための基本機能のテストパターン ○ 本番利用時の業務フローを考慮したテストなど ■ 顧客が操作した際に生成されるデータに起因する処理において 不具合が発生していないか ● 画面表示 ● 内部の分岐処理 ○ E2Eテストが作成できているチームが大半 ○ カバレッジ取得ツールを利用してテストの実施漏れを防止 24

Slide 25

Slide 25 text

#phperkaigi オフショアチームとの作業の進め方 ● 作業ボリュームが大きく作業内容が明確なタスクを依頼 ○ 作業依頼のポイント ■ 依頼する作業は一覧化して認識齟齬が生まれないようにする ■ 調査手順を明確にする ● フォーマットを提供して入力してもらうだけにする等 ○ コミュニケーションのポイント ■ 調査や対応の方針が決められない場合は一緒に検討する ● 調査不可能なほど影響箇所がある場合の対応方針など ■ 疑問点があればすぐに共有してもらうようにする 25

Slide 26

Slide 26 text

#phperkaigi その他の作業 ● リリースまでのマイナーバージョンの監視 ○ PHP8 バージョンアップの対応は長期間に渡る ■ 新しいマイナーバージョンがリリースされていないかを確認 ● PhpStorm のサポートバージョン確認 ○ 古いPhpStorm を利用している方(ライセンス契約上)が対象 ■ PhpStormのバージョンが古くPHP8に対応していない場合は バージョンアップ 26

Slide 27

Slide 27 text

#phperkaigi リリース後 27

Slide 28

Slide 28 text

#phperkaigi リリース後に見つかった不具合① ● デフォルト設定を変更すると特定の画面が表示できなくなる ○ デフォルト設定が変更されることで内部処理にて 0 == “” の比較が発生 ■ 条件分岐の結果が反転 ■ 以降の処理でValueErrorが発生 ● バグ原因:「緩やかな比較の挙動変更」 ○ プロダクトコード全体の 0 == “” の箇所は調査しきれていない ■ ソースの調査では影響箇所を洗い出しにくい ○ 全機能テスト/重要機能テストで拾いたかった ■ 全てのデフォルト設定を変更したテストは考慮できていなかった 28

Slide 29

Slide 29 text

#phperkaigi リリース後に見つかった不具合② ● 文字列のパース処理が想定外の結果になり TypeError が発生 ○ 配列としてパースされるパターンの考慮漏れ ■ 文字列型を期待する標準関数に配列型が渡りTypeErrorが発生 ● バグ原因:「想定外の箇所でTypeErrorが出力された」 ○ テストで全てのパターンを網羅することはできない ■ エラー処理をしっかりと見直しておくべきだった 29

Slide 30

Slide 30 text

#phperkaigi リリース後に見つかった不具合③ ● エラーハンドリングできなくなっていた 30 try {
 $array = null;
 $arrayCount = count($array);
 if ($arrayCount <= 0) {
 throw new Exception('想定外の値が挿入された');
 }
 } catch (Exception $e) {
 echo $e->getMessage();
 }
 PHP8 以前 「想定外の値が挿入された」と 出力 PHP8 以降 Fatal error が発生

Slide 31

Slide 31 text

#phperkaigi リリース後に見つかった不具合③ ● バグ原因の詳細 ○ Warning が発生していた箇所 ■ PHP8 以前:後続処理で Exception を検知 ■ PHP8 以降:TypeErrorが発生 ● TypeError は Error のため Exceptionでは catch が処理されない ● バグ原因:「ハンドリングするエラーの考慮不足」 ○ TypeError が発生する可能性がある箇所はテストで拾い上げたかった ■ しかし、すべてのパターンを網羅するは難しい ○ 想定外のパターンを考慮して例外処理は見直しておくべきだった 31

Slide 32

Slide 32 text

#phperkaigi 不具合発見後の対応 ● 類似バグが他の箇所でも発生しないかを確認 ○ テストで拾えなかった=漏れやすい観点 ■ 可能な限り影響調査 ● 不具合内容はすぐに他サービスにも共有 ○ いずれかのチームで拾えなかった観点は他サービスも盲点 ○ 必要に応じて他サービスも追加調査 ■ 大半はソース上の調査は難しいものばかり ● ValueError や TypeError はソース上の調査では分かりづらい 32

Slide 33

Slide 33 text

#phperkaigi ふりかえり

Slide 34

Slide 34 text

#phperkaigi PHP8 バージョンアップ対応のふりかえり ● 不具合はいくつか発生したがサービスを止めることなく稼働 ○ PHPのバージョンを切り戻すなどの大きな対応は取らずに済んだ ■ 概ねPHP8 バージョンアップは成功したという認識 ● 要因 ○ 基本的な調査が十分できていた ○ 品質担保のテストが十分に実施できていた ○ リリース後に見つかった不具合も速やかに修正してリリースできた 34

Slide 35

Slide 35 text

#phperkaigi 過去のPHP対応工数との比較 ● 弊社の全PHPサービスの PHP8 バージョンアップの工数 ○ PHP7.1 から PHP7.3 へのバージョンアップの対応工数の 約5倍 ■ サービスによっては 約10倍 の対応工数がかかった ■ PHP8 がいかに大きな変更だったかがわかる PHP5.6 → PHP7.1 PHP7.1 → PHP7.3 PHP7.3 → PHP8.0 ※8.7人月 8.2人月 42.1人月 ※PHP5.6 → PHP7.1のバージョンアップの工数には  1サービス分の工数が含まれていません 35 弊社の全PHPサービスの合計工数

Slide 36

Slide 36 text

#phperkaigi まとめ ● PHP7.3 系から PHP8.0 へのバージョンアップを実施 ● 5サービスごとの対応工数は PHP7.1 → PHP7.3 と比較して 3倍~10倍だった ● リリース後にいくつかの不具合が発生したが大きな問題も無く対応 が完了 ○ PHP8バージョンアップは概ね成功という形で終えることができた 36

Slide 37

Slide 37 text

#phperkaigi 最後に ● PHP8 バージョンアップの代表者へのヒアリングで出た意見 ○ 近年のPHPは安全性を高める(型を意識した)変更が立て続けに導入されて いるが、今回はかなり思い切ったバージョンアップという印象 ○ 様々な新機能を使えるようになるのが楽しみ ● 私の所感 ○ 対応が完了するまでは下位互換性のない変更と真摯に向き合い 対応完了後は新機能を取り入れようとする思考に切り替えていく ■ セキュリティサポートを得ることだけを目的と捉えない 37

Slide 38

Slide 38 text

#phperkaigi 新機能が利用できるというポジティブな気持ちで バージョンアップに取り組むことが重要 38

Slide 39

Slide 39 text

#phperkaigi ご清聴ありがとうございました!

Slide 40

Slide 40 text

#phperkaigi 参考資料 ● PHP Conference 2021, 「レガシーシステムにおけるPHP8バージョンアップのアプリ対応記」 ○ https://speakerdeck.com/bosshawk/regasisisutemuniokeruphp8baziyonatupufalseapuridui-ying-ji-lu-ad4444bf-51f5-4379-86c3-cddb38163a3c , (参照 2022-03-23) ● PHP Conference 2021, 「20年モノの巨大Webサービスの開発継続戦略 - ミドルウェアのバージョンアップとの向き合い方」 ○ https://speakerdeck.com/penguin045/20nian-mofalsefalseju-da-websabisufalsekai-fa-ji-sok-zhan-lue-midoruueafalsebaziyonatuputofalsexiang-ki he-ifang , (参照 2022-03-23) ● Rakus Tech Conference 2022, 「息の長いサービスの PHP8バージョンアップで見えた 課題と解決法」 ○ https://speakerdeck.com/bosshawk/problems-and-solutions-found-when-upgrading-long-term-services-to-php8 , (参照 2022-03-23) ● php.net, 「PHP 7.3.x から PHP 7.4.x への移行」 ○ https://www.php.net/manual/ja/migration74.new-features.php , (参照 2022-03-23) ● php.net, 「PHP 7.4.x から PHP 8.0.x への移行」 ○ https://www.php.net/manual/ja/migration80.new-features.php , (参照 2022-03-23) ● qiita, 「【PHP8.0】非厳密な比較演算子`==`の挙動が今さら変更になる」 ○ https://qiita.com/rana_kualu/items/82cc8295d2102d14b88a , (参照 2022-03-23) 40