PHP標準関数との戦い/Fighting with PHP standard function

B3a78cfbf06ca9ee860536c6d1de5e7f?s=47 Y-KANOH
August 05, 2020

PHP標準関数との戦い/Fighting with PHP standard function

PHPを使っているサービスにとって、避けられない問題がPHPのバージョンアップです。
歴史が古いレガシーなシステムであればあるほど、バージョンアップ時の仕様変更に対応することが難しくなっていきます。
サービス開始から20年近く経っているメールディーラーの歴史もPHP バージョンアップとの格闘の連続でした。
今回は、そういった闘いの一例として PHPの標準関数の仕様変更対応をご紹介します。
何の変哲もなさそうな仕様変更の裏で行われた失敗と改善の記録をお楽しみください。

B3a78cfbf06ca9ee860536c6d1de5e7f?s=128

Y-KANOH

August 05, 2020
Tweet

Transcript

  1. #RAKUSMeetup ©2020 RAKUS Co., Ltd. PHP標準関数との戦い 加納 悠史

  2. #RAKUSMeetup 加納 悠史 カノウユウジ PHPer 趣味はダイビング 業務内容:メールディーラー の開発 @Ykanoh65 株式会社 ラクス

  3. #RAKUSMeetup メールディーラーとは 問合せメールを一元管理! 顧客対応サポートツール

  4. #RAKUSMeetup メールディーラー • 2001年にサービス提供開始 • 6,000社以上の導入実績 • メール/Webマーケティング市場 11年連続シェア No.1

    !!※ ※ 出典:ITR「ITR Market View:メール/Webマーケティング市場 2020」メール処理市場ベンダー別 売上金額シェア 2009-2019年度(予測値)
  5. #RAKUSMeetup メールディーラー • 2001年にサービス提供開始 • 6,000社以上の導入実績 • メール/Webマーケティング市場 11年連続シェア No.1

    !! ⇒ 19年モノ の レガシーシステム
  6. #RAKUSMeetup 標準関数との戦い

  7. #RAKUSMeetup count() 関数

  8. #RAKUSMeetup ことの始まり PHP バージョンアップ • v7.1 → v7.3 • count()

    関数の仕様変更を発見
  9. #RAKUSMeetup count()関数の仕様変更 PHP 7.2 以降の挙動  → カウントできない型を引数にすると Warning が発生 print(

    count(1) ); // 出力:1 print( count("hoge") ); // 出力:1 print( count(null) ); // 出力:0 Warning: count(): Parameter must be an array or an object that implements Countable in %s on line %d code php.log
  10. #RAKUSMeetup 対応案 すべての count() 関数に対して... A. カウント可能な値のみ渡すよう修正 - そもそも NULL

    や int をカウントしている方がおかしいでしょ B. カウントできない値も許容するオリジナル関数で置換え - 全 count() 関数なんて見てられない - オリジナル関数で NULL や int は 0 や 1 を返せばOK
  11. #RAKUSMeetup count() の数を確認

  12. #RAKUSMeetup 2,232 個所

  13. #RAKUSMeetup 対応案 A. カウント可能な値のみ渡すよう修正 - そもそも NULL や int をカウントしている方がおかしいでしょ

    B. カウントできない値も許容するオリジナル関数で置換え - 全 count() 関数なんて見てられない。 それが現実。 - ラッパー関数にて NULL や int は 0 や 1 を返せばOK 無理... すべての count() 関数に対して... 採用
  14. #RAKUSMeetup 採用した対策案 • 自作の関数 mdCount() を作成 ◦ 共通関数として 共通関数ファイルに定義 •

    すべての count() 関数を置き換え 一件落着...
  15. #RAKUSMeetup 対応完了後の コードレビューにて... あれ...?

  16. #RAKUSMeetup 第一ノ問題 消したはずの count() 関数が増殖している

  17. #RAKUSMeetup なぜ復活していたのか? • 並行開発されていたブランチからの混入 • ルール周知不足による使用 ⇒ 使わせない仕組みがなければ増殖する

  18. #RAKUSMeetup 対策 プロセスによる対策 • コーディング規約に追加 • コードレビュー観点に追加 技術的な対策 • 自動検出(検討中)

  19. #RAKUSMeetup 第二ノ問題 オリジナル関数を 使えない処理の存在が発覚

  20. #RAKUSMeetup おさらい:採用した対策案 • 自作の関数 mdCount() を作成 ◦ 共通関数として 共通関数ファイルに定義 •

    すべての count() 関数を置き換え
  21. #RAKUSMeetup おさらい:採用した対策案 • 自作の関数 mdCount() を作成 ◦ 共通関数として 共通関数ファイルに定義 •

    すべての count() 関数を置き換え
  22. #RAKUSMeetup おさらい:採用した対策案 • 自作の関数 mdCount() を作成 ◦ 共通関数として 共通関数ファイルに定義 •

    すべての count() 関数を置き換え 問題の処理では 共通関数ファイル を読み込んでいなかった!
  23. #RAKUSMeetup 共通関数を読み込んでいない... メールディーラーの構成 • オートロードは利用していない ◦ クラス化されていないため利用不可 • 共通関数ファイルは 処理の先頭で読み込むルール

    ◦ 例: ◦ 不用意に読み込みファイルを増やすと危険 require_once "common.php"; 関数名の競合など...
  24. #RAKUSMeetup 対応策 新しい共通関数ファイルを作成 ⇒ すべての処理で読み込むようにする 1. mdCount2()(仮称) を作成 2. 旧

    mdCount() は mdCount2() を呼び出すよう変更
  25. #RAKUSMeetup 対応策 新しい共通関数ファイルを作成 ⇒ すべての処理で読み込むようにする 1. mdCount2()(仮称) を作成 2. 旧

    mdCount() は mdCount2() を呼び出すよう変更
  26. #RAKUSMeetup 対策:auto_prepend_fileを活用 auto_prepend_fileとは • php.ini の設定値 • メイン処理の前に実行するファイルを指定 → コードの修正を少なく前処理を定義することが可能

    https://www.php.net/manual/ja/ini.core.php#ini.auto-prepend-file
  27. #RAKUSMeetup 対策:auto_prepend_fileを活用 1. mdCount2()(仮称) を作成 → auto_prepend_file で読み込み 2. 旧

    mdCount() はどうする?   → mdCount2()(仮称) を呼び出すよう変更 修正コストを抑えつつ対応することに成功
  28. #RAKUSMeetup まとめ / ふりかえり 対応が完了しても安心してはダメ! 今回の対応策 • count() の仕様変更 ⇒ オリジナル共通関数で対応

    • count() が増殖 ⇒ 規約とコードレビューで防止 • オリジナル関数が呼べない ⇒ 新しい共通関数ファイルを作成 • 新共通関数ファイル呼び出し ⇒ auto_prepend_file の活用で対応 今後の対応 • 増殖防止策:ツールによる自動検知を導入 • 共通関数ファイルの mdCount() の廃止 再発の芽を完全に摘む為に、 count()との闘いはもう少しだけ続く・・・・