コネヒト開発者ブログ

コネヒト開発者ブログ

PHPフレームワークのバージョンアップを支える技術

GWいかがでしたでしょうか?
私はひたすらに「何もしないぞ!」を徹底した結果、漫画を読んで「色々な世界を旅して」「色々な出会いと別れを繰り返して」過ごしました。

その中でも、「子供はわかってあげない」を久しぶりに読み返し、

人は教わったことなら教えられるんだよ

からの

僕は誰にも教わってないんだ。・・・だから教えるのが難しいの当たり前なんだなって思って・・・少し楽になったかな

というくだりにグッと来ました。
「教えられることがある」や「教えてくれる」というのは、それだけで尊いものだな〜というリスペクトを忘れないようにしようと思うのと同時に、自分や他人の「持てるもの・持てないものを見極めて、許したり奮ったりしてけたらな」とも思いました。
なんにせよ、他人に教えられる人はすごい!🎉

子供はわかってあげない(上) (モーニング KC)

さて、こんにちは、サーバーサイドやっております 金城 (@o0h_)です。

今回は、「PHPフレームワークのマイナーアップデートをしようとしたら少し大変だったので、工夫したこと・やったこと」のお話をします。

TL;DR

  • CIの活用 / (ユニット)テストを日頃から充実させておく
  • Migration GuideやPRに目を通して、影響範囲を予想する。対応イメージを考える
  • ツールによる自動対応が可能なモノは積極的に活用する
  • 「どっかで何かが起きた」ときのgit-bisect
  • リグレッションテストの計画と実施

https://cdn-mamari.imgix.net/item/500x0_5cd98323-e73c-4933-8afd-0036ac110002.jpg.jpg

コネヒトのサーバーサイドアプリケーションは、原則として「コマメに依存パッケージのアップデートをする」戦術を採っています。
が、ママリのメインとなるバックエンドアプリケーション (api)は諸々の事情で更新が遅れていました。
具体的に言えば、CakePHPの 3.6.xで稼働させており、 3.7.xに移行できていなかったという状況。

3月末に私がチラッと手出しをしかけていたものを、本気を出した @supermanner によってマージ可能な状態まで持っていってくれた・・・!ということで、夢が叶いつつあります。

この「CakePHPの移行」をケーススタディとしつつ、「フレームワークのバージョンアップ面倒くさいよねどうやろっか」はあるある話だとも思うので、なるべく問題を一般化して振り返ってみたいと思います。

1. CIの活用 / テストを日頃から充実させておく

ある程度の単体テストが充実していれば、細かな修正などについては原則として「CIが通ればOK」「テストで保証しにくい、外部システムとの連携部分だけ目で確認する」といった方針が取れます。 それと同時に、静的解析などの活用も「安心できるコード」を支えてくれるので、積極的に扱っていきたいところです。
例えば phpstan-deprecation-rules は、廃止予定となったメンバ等の利用を検知してくれます。

github.com

また、静的な解析だけでは難しいルール違反も、テストカバレッジを高めておけば動的に「機械が弾いてくれる」状態を実現できます。
一例としては、「引数によってはメソッドの利用方法が推奨されなくなっている」場合などです。

実際に、CakePHPでは「メソッド自体は有効だが、アクセサとしての利用が非推奨/ミューテータとしての利用だけが認められるようになったため、引数無しでのコールをE_DEPRECATEDを発火する」といった変更も多々行われています*1
こうした場合には、「テスト実行時に実際にエラーとなる」状態が担保されていると検知も簡単です。

2. 変更内容を確認する

多くの場合、バージョンごとの「変更内容」がまとめられていると思います。 CakePHPの場合、「パッチバージョンの場合は公式ブログ(Bakery)を見る」「マイナーバージョンの場合はMigration Guideを見る」ことで、その内容を簡単にキャッチアップすることができます。

例: CakePHP3.7.6の場合
https://bakery.cakephp.org/2019/04/09/cakephp_376_released.html

例: CakePHP 3.7.xの場合
https://book.cakephp.org/3.0/en/appendices/3-7-migration-guide.html

これらの内容に目を通すことで、「自分の利用しているアプリケーションに関係ありそうな変更はあるか」「deprecatedやnew featureはあるか」といったイメージが掴めますし、それに応じて「どのくらい大変そうかな?」というザックリとした"覚悟"もできると思います。
*2

CakePHPでは、3.7/3.8については「4.xに向けて大きな変更をしない、廃止予定のコードやAPIの整備を進めていく」という流れで動いています。*3

4.xへのアップグレードは、これらの「廃止予定となっている内容を利用していなければ、スムーズに行えるようにする」と説明されているので、今の段階から少しずつ着実に対応していこうというのは大きなモチベーションとなるわけです。

3. 自動修正ツールの積極的な活用

CLIツールの利用

「大きなロジックの変更ではないが、地味に面倒・・・」という修正は、フレームワークのアップデートにつきものです。
例えば、3.7.0から「テストケース中で利用するFixtureの宣言は、snake_caseではなくCamelCaseで行うように」という変更が入りました

Throw warning when using underscored fixture names. · cakephp/cakephp@f86a585 · GitHub

すると、ほぼ全部のテストクラスが対象となるので、手でいじるのがめちゃくちゃ面倒くさい・・・・・・・・
嬉しいことに、こうした変更に対して「CLIからワン・コマンドで自動修正してくれる」というツールがあります。

Cakeのコアメンバーであるdereuromark氏が独自にメンテナンスしているupgrade toolはその1つです。 github.com

また、個人的に最近注目しているのはrectorというツールです。 getrector.org

これについては、昨年のConnehito Advent Calendar中でも @fortkle が触れていました。

fortkle.hatenablog.com

現在、rectorはsnake -> camelの変更に対応しているので、気の遠くなるような作業もほんの数分足らずで終了することができました・・・

f:id:o0h:20190513230542p:plain
当該のコミットの様子。これを手でやっていたら大変・・

他にも、php-cs-fixerなどのツールも、便利に利用することができると思います。
これらの自動整形ツールは、間違いなく人間のストレスを軽減しますし、また作業内容の確実性の担保という意味で実装者・レビュアー双方に対して安心感を提供することになります。

開発用ツールの気軽な導入のために

便利そうなツールを発見し、いざ導入しようとすると依存パッケージのコンフリクトによって阻まれることもあります。
勝手なイメージですが、php-parserなどはよく衝突するのではないでしょうか・・・・

そうした場合に便利なのが、 composer-bin-pluginです。

github.com

これは、「vendorやcomposer.lockを、任意の名前空間ごとに独立させて扱ってしまおう」というもので、
例えば「phpstanの実行に必要なvendor」「rectorの実行に必要なvendor」とった具合に、互いを隔離して利用することが可能です。

具体的なユースケースとしては、dereuromark氏がCakePHP3.6リリース時に投稿しているエントリーにて言及があります。

CakePHP 3.6 is coming – DerEuroMark

また、先日のPHP勉強会@東京のLTにて個人活動として紹介もさせていただいたので、よろしければ御覧ください。

what-is-composer-bin-plugin - Speaker Deck

IDEの活用

先程、「今回のアップデートは、主にDeprecatedへの対応となる」という言及をしました。
その際に大活躍したのがPhpStormです。
「deprecatedと思われる機能を一括検索する」という機能を備えています。

Code > Run Inspection By Name から Deprecated(PHP|General)を実行すると・・・一網打尽です!

f:id:o0h:20190513231452p:plain

Jet Brainsの記事も併せて御覧ください。

www.jetbrains.com

4. git-bisectの利用

「PJ全体」「多くのファイルを一括して」変更を行っていくと、思わぬ箇所でエンバグしていることもあります。

git-bisectの利用は、「大雑把にかつ的確に問題箇所を特定する」のに役立つかもしれません。

git-scm.com

これは、「最新〜ある地点のコミット」という限界範囲を決めた上で、「ここは正しい・・・ここはおかしい・・・ここは正しい・・・」とyes/noをマークしていきながら、二分探索により最終的に「問題が初めて生じた地点」を発見していくというものです。

5. リグレッションテストの計画と実施

最終的には、「ユーザー観点に立って」「実施に操作をして」、提供されるべき機能が失われていないか・壊れていないかをチェックします。 どんな項目をチェックするか?というのを、実際に練っておくことで、より安心してリリースを迎える事ができます。

コネヒトにはQA専任のチームなどは持っていないので、アプリケーション開発者が自ら&相談しながら項目の洗い出しを行います。
中心になるのは、「主要・重要な機能」「今回の変更内容によって影響が考えられる機能」といった観点でしょう。

f:id:o0h:20190513232702p:plain

コードレビューやテストコードだけでは見えてきづらい部分についても、人の目によって不安を払拭することができます。

まとめ

「コネヒトでスムーズにライブラリやフレームワークの更新を行うために気を遣っていること」を紹介しました。
大方針としては、 安心感を担保するために、使えるものを積極的に使う という表現に集約されると思います。
「自動化ツール(CIやコマンドツール,IDE)の活用」や「人の目を介す箇所の意義設定・デザイン」というのは、あくまでそうした目的を果たすための手段!と言えるのではないでしょうか。

とりわけ、「CIで保証できる範囲・内容を広げておく」「便利なツールを探し、積極的に使う」というのは、対象のフレームワークや言語に限らず共通している価値観だと考えています。
もし手法の未確立や効率化不足がおこると、アップデート作業というのはとても億劫になってしまいます。
更に、「前回の更新から大きく日が開いてしまっている」という状況は、ますます億劫さを加速します。

そうなってしまう前に、チョコチョコとアップデートを行ってみる!というのはいかがでしょうか。

*1:例えば https://github.com/cakephp/cakephp/blob/3.7.7/src/Validation/ValidationSet.php#L65 など

*2:余力があれば、実際のコミットも探して内容を見てみる・納得できるようにしてみる、というのも大きな学びになると思います。個人的には、Bakeryにパッチバージョンのリリース情報が掲載されたタイミングで、「ここで言及されている内容はどのようなコード修正になるのだろうか」というのを追うようにしています

*3:内容についてはRoadmapに詳しいです