新卒一年目の永井です。今回はGitHub Actionsで実行されるPHPStanについて、レガシーなソースコードで改善されずにいたエラーを改善し、キャッシュを使えるようにして実行時間を大幅に削減できたことを書いていきます。
はじめに
弊社ではPHPStanをGitHub Actionsで回しています。タイミングはPull Requestにpushした時やPull Requestをメインブランチにマージした時です。これにより、通常は静的解析をしてコードの品質を維持してPull Requestをマージすることができます。しかし、これにはいくつか問題点がありました。
問題点
改善できていないPHPStanのエラーがある
昔に書かれた弊社のレガシーなソースコードなどでPHPStanのエラーが存在している状況でした。そして、PHPStanはプロジェクト全部をチェックするため、レガシーなソースコードで生じたPHPStanのエラーが関係ないPull Requestで発生してしまい常にワークフローが失敗してしまうという問題がありました。このことから、ワークフローは必ず成功するように以下のようにしていました。
vendor/bin/phpstan analyze | truePHPStanでエラーが出ても気づけない
PHPStanのワークフローが失敗しないようになっているので、差分についてもしPHPStanの解析結果にエラーがあってもPull Requestのマージブロックができません。よって、レビュワーもレビュイーも気付けずにそのままマージされる可能性が常にありました。気づくためには以下のように、Pull RequestのFiles changedページにて表示されているのを見てPHPStanの実行結果でエラーが出ていることを確認する必要がありました。

キャッシュを使うことができない
また、PHPStanのエラーが改善されていなかったことからPHPStanのキャッシュが利用できませんでした。なので、PHPStanのワークフローの実行時間が長く、10分程度かかっていて開発者の負担になっていました。それにより、Pull Requestの作成者はPHPStanのワークフローの実行が完了するまで待てず先にレビュー依頼をするということも起こっていました。
GitHub Actionsの料金が多くかかってしまう
さらに、弊社では最近GitHub Actionsの料金が無料枠に収まらないことが多く、ワークフローの実行時間の削減が必要でした。Pull Requestにpushしたり、メインブランチにマージするたびにCIが実行されるため、一日に多くのワークフローが実行されます。先ほど書いた通り一つあたり10分程度かかります。そのため、課金のペースも早くなっています。
改善策
現時点でのPHPStanのエラー(主にレガシーコードの部分)を改善し、キャッシュが使えるようになれば、実行時間を削減できて上記の課題は解決できると思いました。
PHPStanのエラーを改善する
弊社では、週に1度開発本部内の人全員が参加する技術共有定例というものを行なっています。

そこで、私が「PHPStanのワークフローでキャッシュを使いたいたいです、そのためにはまずPHPStanのエラーの改善が必要なのでみんなで直していきましょう」ということを共有しました。その後、みなさんのおかげでエラーは改善され、以下のようにPHPStanのワークフローで必ず成功するという処理を外すことができました。
vendor/bin/phpstan analyze
また、PHPStanの実行結果でエラーが出たら、ワークフローが失敗するようになったので差分が出た箇所についてレビュワーもレビュイーも気付けるようになりました!
しかし、まだワークフローの実行時間が長いので依然として待つことに負担がかかります。また、GitHub Actionsの料金削減もできていません。よって次に、PHPStanのキャッシュをGitHub Actions上で使えるようにします。
GitHub Actions上でPHPStanのキャッシュを使えるようにする
PHPStanのキャッシュについて
まずは、PHPStanのキャッシュについて見ていきます。基本的には以下の公式ドキュメントがとても役に立ちます。
公式ドキュメントに書いてある通り、PHPStan実行時にエラーがある場合次の実行でキャッシュは利用されないです。しかし、先ほど書いたように弊社ではPHPStanのエラーを改善できたのでキャッシュは使えるようになりました。
PHPStanのキャッシュについての設定はphpstan.neon,phpstan.neon.dist,phpstan.dist.neonの三つの設定ファイルのどれかに書くことになります。そして、その中のtmpDirというパラメータに設定されるパスがキャッシュの保存先になります。tmpDirパラメータはデフォルトでは./tmpというファイルパスですが、今回はtmpディレクトリは既に他の用途で使われているため%rootDir%/tmpを使うことにしました。%rootDir%/tmpは実際には./vendor/phpstan/phpstan/tmpというファイルパスになります。
以上より、PHPStanのキャッシュの使い方は分かったのでGitHub Actions上でこのPHPStanのキャッシュをどのように永続化や取得、更新をするのかについて考えていきます。
GitHub Actions上でキャッシュを永続化して取得、更新をする
GitHub ActionsでPHPStanのキャッシュを永続化し取得や更新する方法。以下のactionsを使います。
このactionsのactions/cache/restore@v3 とactions/cache/save@v3を使います。なぜactions/cache@v3を使わないのかというと、キャッシュの更新のタイミングを柔軟に操作できないからです。今回はPHPStanのキャッシュの更新タイミングをPull Requestがメインブランチにマージされた時にしていくので柔軟なキャッシュ更新が必要でした。そして、キャッシュの取得と更新にはキャッシュとするパスとキーを指定しなければなりません。
パスについて
これは先ほど述べたPHPStanの設定ファイルのtmpDirパラメータに指定したパスをここで指定します。今回はtmpDirには%rootDir%/tmpと指定してるので、ここで指定するのは./vendor/phpstan/phpstan/tmpとなります。
キャッシュキーについて
キャッシュキーによってどのキャッシュを取得するか、どのキャッシュを更新するかを決めることができます。キャッシュキーの指定にはkeyとrestore-keyがあります。

keyは完全一致です。keyに一致しなかった場合restore-keyに指定されているキーが選ばれます。restore-keyは部分一致ができて、複数一致した場合は最新のキャッシュが取得されます。また、actions/cache/save@v3のkeyには一意な文字列を指定しなければなりません。しかし、keyに毎回一意なキャッシュキーを指定すると、actions/cache/restore@v3のkeyではそのキャッシュキーが指定できないので毎回キャッシュを取得できません。
ここで、restore-keyです。actions/cache/restore@v3にrestore-keyを使えば部分一致になりさらに最新のキャッシュを取得できます。つまり、phpstan-result-cache-{一意な文字列}というキーをrestore-keyに指定すれば、良いことになります。
以上を踏まえると、PHPStanのキャッシュを永続化や取得、更新を行うワークフローは以下のように書くことができます。
- name: "Restore result cache"
uses: actions/cache/restore@v3
with:
path: ./vendor/phpstan/phpstan/tmp
key: "phpstan-result-cache-${{ github.run_id }}"
restore-keys: |
phpstan-result-cache-
# … run phpstan
- name: "Save result cache"
uses: actions/cache/save@v3
if: github.event.pull_request.merged == true
with:
path: ./vendor/phpstan/phpstan/tmp
key: "phpstan-result-cache-${{ github.run_id }}"これで、PHPStanのキャッシュをGitHub Actions上で使えるようになります!PHPStanの実行では、キャッシュを基本使い、差分が出ているところはその差分だけ解析してくれて、他の部分はキャッシュを使い、差分でエラーが出ればちゃんと実行結果が失敗となります。
参考

GitHub Actions上でPHPStanのキャッシュを利用できるようにした結果
今まではPHPStanのワークフローの実行時間が10分かかっていたものが、早くて数十秒、長くても2、3分程度で終わるようになりました!

まとめ
以上より、最初に挙げていたPHPStanのワークフローの実行終了まで待つ負担も減り、PHPStanのエラーを見逃すこともなく、GitHub Actionsの実行時間削減にも貢献できました。
社内では、改善した方が良いものがその時はやむを得えない理由や優先順位的にそのままになっていることがあります。それが日が経つにつれて、改善しない理由がなくなった時にはその状況が当たり前になっていることもあります。なので、社内の今の状況や歴史も把握した上で常になぜそうなっているかを意識して、これからも弊社にとってより良い環境になるよう行動していきたいと思います。


