GitHub ActionsでDocker container actionを実行する際のイメージ名・タグに意味はほとんどない

前提

  • Runner Version: 2.304.0

まとめ

  • Docker container actionを実行する際は毎回BuildステップでDockerイメージをビルドし、そのイメージを実行している
  • ビルドされるイメージ名・タグに意味はほとんどない
  • どの時点のソースが実行されているか確認したい場合はSet up jobステップの「Download action repository」に表示されるコミットハッシュを見るのが確実

経緯

私はGitHub Actionsでgha-usageという自作のDocker container actionを公開し自分のリポジトリで使っているのですが、そのアクションは慣習(?)に則りタグを打つ毎にv1というタグの内容を差し替え、使う側でバージョン指定を書き換えなくても1.x系のの最新バージョンを使えるようにしています。
(タグの差し替えはaction-update-semverを使用)

そしてgha-usageのバグを修正したにも関わらず実行ログを見ると修正が当たっていないように見え、もしかしてタグの差し替え失敗しているのでは?と思いタグが何を表しているのか調べてみました。

詳細

GitHub Actionsのランナーはソースが公開されています。

github.com

そして、GitHub Actionsの実行時に出力されるログで全文検索を掛けてみるとDocker container action実行時は

src/Runner.Worker/ActionManager.cs#L572

が実行されているようです。

まず、584行目の

var imageName = $"{dockerManager.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";

からDockerイメージのタグにはGUIDが使われていることが分かります。

そしてイメージ名に使われているdockerManager.DockerInstanceLabel

src/Runner.Worker/Container/DockerCommandManager.cs#L49

で値が初期化され、他に代入している箇所はありませんでした。

DockerInstanceLabel = IOUtil.GetSha256Hash(hostContext.GetDirectory(WellKnownDirectory.Root)).Substring(0, 6);

どうやら何かのディレクトリからSHA-256のハッシュを生成していそうです。

と辿り、Runnerの実行ファイルが設置されているディレクトリの親ディレクト絶対パスからハッシュを生成していることが分かります。

という訳で、Docker container actionのイメージをビルドする際のイメージ名・タグはどちらも該当アクションに関係無い値が使われていました。

と、ここまでソースを読んだ後に実行ログをよく見たら「Set up job」ステップの方にGitのコミットハッシュが記載されている事に気づきました・・・
(そして修正が当たっていないように見えたのは単に修正前に実行されていたからだった・・・)

ログはちゃんと見ようね・・・!

参考

PHPerKaigi 2023にコアスタッフとして參加しました

3月23日から25日にかけて開催されたPHPのカンファレンス、PHPerKaigi 2023にコアスタッフとして參加しました。

phperkaigi.jp

PHPerKaigiは2019年から参加しており、2020年・2022年は当日スタッフとして参加していました。
そして、今回は9月頃にコアスタッフにお誘い頂き、自分が何か力になれるなら・・!とコアスタッフとして参加することにしました。

事前準備

事前準備ではノベルティボックスに封入されるパンフレットや当日会場で提供したオリジナルせんべいの制作、参加方法を紹介するブログ記事の作成などを行いました。

自分だけが持っているであろうサンプルカラーのお煎餅と共に

パンフレット

印刷物の制作経験はありませんでしたが、昨年のパンフレット格好良かったな、と手を上げてみました。

最初は「無線綴じ・・・?」「PP貼・・・?」という状態でしたが、PHPerKaigiは過去の対応内容がIssueとして残っているのでそれを参考にしながら制作を進め、デザイナーさんが素敵なデザインを作成して下さり、かっこかわいいパンフレットを作れて大満足です。

ただ、デザイナーさんに依頼するのがギリギリになってしまったので、もう少し余裕を持って進めれば良かったなと反省しています。

PHPerせんべい

kotomin_mさんの「オリジナルマカロン作りたい、しょっぱいのも欲しい」という提案をきっかけにしょっぱいオリジナルお菓子何か無いかな、と探してお煎餅を作りました。
(kotominさんは他にもPHPerKaigi 専用Twitterアイコンも提案・実行していてすごい・・!)

当日会場ではこのように配布され、好評だったようで何よりです。

参加方法紹介ブログ

会期10日前に前回の振り返り結果が発掘され、「参加方法の紹介記事要るのでは」となり2020年の紹介記事をベースに今年の新しい取り組みなどを反映した記事を作成しました。

blog.phperkaigi.jp blog.phperkaigi.jp

WordPressを久しぶりに触って浦島太郎になっているPHPerの図

アンケートでも参考になったとの声がありとても嬉しいです。
一方、ブログに気づかなかったとの声もあったので、来年はより多くの人に伝わるようにしたいですね。
(良い方法思い浮かんでいませんが・・・)

当日

無線機

これまでPHPerKaigiではスタッフ間の連絡にSlackを使っていたのですが、今年はそれに加えて無線機を使うようになり、その管理を担当していました。
スタッフぽく見えるようになって雰囲気が出るので今年はTrack Dを追加し会場を広く使う形になるので)

厳密に言えば事前準備から関わってはいましたが、iOSDC Japanで利用した業者さんへの打診や発注は実行委員長の長谷川さんが済ませて下さっていたので、ほとんど当日の運用だけ考えれば済みました。

無線機は各スタッフに特定の端末を割り当て毎日返却してもらう運用にしており、充電器に養生テープでそれぞれの無線機の番号を記載し、どの無線機が未返却なのか一目で分かるようにしました。

養生テープをどこかに貼って未返却の無線機を可視化出来たらなと何となく考えてはいたのですが、当日充電器を見てみると丁度いいスペースがあり、上手くハマったと思います。

無線機は実際使ってみると、思っていた以上に便利でした。

何か作業をしていたりする時はSlackのメッセージを確認出来なかったり、Slackに文字を打ち込む時間が惜しかったりするのですが、そういったときでも何となくスタッフ間のやり取りを把握出来たり気軽に連絡事項を伝えられたりするので、とても捗ります。

ただ、無線で流れている会話に意識を一定割合持っていかれたり長いやり取りに不向きだったりするので、使い分けが肝心だなとも思います。

HQ

今年はHQ(Head Quarter)担当として、

  • Informationコーナーで落とし物の対応をしたり
  • Twitterやニコ生から参加者の方の声(音声小さいなど)を拾ったり
  • Twitterで各種告知をしたり

していました。

HQは初めてだったので緊張していたのですが、HQ経験がある方と一緒だったので安心して担当出来ました。

HQ担当をするようになって知ったのですが、TweetDeckのチーム機能を使うと認証情報を共有しなくてもあるアカウントを複数人で使えるようになるんですね。

help.twitter.com

便利。

Wi-Fiルーター受取

参加者向けに提供していたWi-Fiがday 0でダウンし、急遽業務用のWi-Fiルーターをレンタルすることになりました。

そのWi-Fiルーターを受け取りに行く所からday 1はスタート。

受け取りのために向かった業者さんの事務所はレンタル機器用倉庫の一角にカウンターが設けられている感じで、そういった普段関わらないイベント運営関係の業者さんを知れるのもカンファレンススタッフの醍醐味の1つですね。

Wi-Fi復旧後はほとんど安定して動いていたようで何よりでした。

Track D

今年のPHPerKaigiではday 2限定でTrack Dとしてポスターセッション・スポンサーブースB用の部屋をオープンし、そのTrack D担当もしていました。
(と言っても、設営・撤収作業を他のスタッフの方と進めたり換気に気を配ったりした位ですが)

少し離れている位置にあり導線も分かりにくい部屋だったので参加者の方が来てくださるかが一番の心配事でしたが、uzullaさんが色々と考えてくださったこともあり賑わって良かったです。

1参加者目線でも、ポスターセッションは展示してあるポスターが会話のきっかけになり、初対面の人に話しかけづらい自分のような人間に優しい企画かなと思いました。

(開催されたら)来年もTrack Dをオープン出来たら良いな〜と思っています。

見たトークなど

fortee.jp

自分もニッチではありますが自作OSSを公開してるので、「わかるー、趣味OSS気軽に公開してこうぜ」って首をブンブン縦に振りながら読んでました。


fortee.jp

なんとなくキャッシュしよう、なんとなくRedis使おうと考えてしまいがちなので、IOバッファ/演算バッファに分けた説明がとても分かりやすかったです。

「あー、演算結果をプロパティに保存して再使用するのは演算バッファを無意識に考えていたのか」とか「確かにDBからRedisに変えても同じネットワーク越しのアクセスならあまり距離変わらないな」とか。


fortee.jp

runnの存在は知っていたものの「ウェブフレームワークに備わっているAPIテストの仕組みと比べた時の嬉しいポイントなんだろう」と思っていたのですが、発表で背景を聞きすっと腹落ちしたのが個人的に爽快でした。


fortee.jp

PHPでもここまでいけるんだ、と勇気を貰えました。
また、イメージで「PHP遅い」と言うのではなく、計測するの大事だなと思いました。
(他にも計測の重要性に触れた発表ありましたね)


fortee.jp fortee.jp

これは自分の勝手な思い込みかもしれませんが、Rectorは何かコードをガッと変更したい時に単発で使うイメージでした。

そのため、RectorをCIで使う発表を2つ見て「そういう使い方あるのか、便利そう」とRectorに対する興味度合いが上がっています。


fortee.jp

私は型をかっちり書きたい派なのですが、「Eloquent、うーん・・・。型はかっちり書きたいけど、Laravel使ってるのに独自実装したら意味ないんじゃないか」とモヤモヤしていました。

なのでこの発表はとても気になっており、発表を聞き「確かにレールに乗ったほうが良いし、不便に感じているのはLaravelの機能を使い切れていない部分もあるのだろうな」と感じ、Laravelへの向き合い方の助けになりました。


fortee.jp

デジタルサーカスさんのPHPerチャレンジ問題を解いてから「解けはするけどなんだこの問題、すごいな」と思っていたので解説を聞けて大満足でした。

↑一番笑ったとこ

まとめ

今年は初めてコアスタッフとして参加しましたが、色々やれて楽しかったです。

参加者の皆さんも楽しんで頂けたようならとても嬉しいです。

ただ、自分から提案して進めたというより面白そうなのを拾ったりしたといった感じなので、来年はもっと自分発で動けたらと思います。
(と言いつつ、某バンドアニメのTシャツを買ってからカンファレンスTシャツにオリジナルのネームタグを付けたい欲がむくむくと湧き上がってきている今日この頃)

そしてここ半年(特に直近約3ヶ月)はPHPerKaigiが脳の一定割合を占めていたので少し休みたい気分半分、逆にスタッフをやっていない生活に違和感を覚えそうな気持ち半分、といった所です。

なので9月10月頃になって余裕があればアレとかアレとか当日スタッフに応募してみようかなーとぼんやり考えています。

というわけで、PHPerKaigi 2023の振り返りでした。

(開催されたら)PHPerKaigi 2024で会いましょう!

2022年の振り返り

1・2月

Nature RemoのAPIを叩いて部屋の気温・湿度を定期的に記録するためのツールを作り始めたのが1月でした。

github.com

GitHub ActionsでDockerコンテナとして動かしているのですが、セットアップ方法など記憶が曖昧になっているので記事にまとめておけば良かった感。

また、前々から気になっていたLeanとDevOpsの科学を2月に読みました。
具体的に業務に活かせている訳ではないですが、聞きかじりの知識で誤まった理解をしなくて済むようにはなったかなと。

4月

4月はPHPerKaigi 2022に参加しました。

muno-92.hatenablog.com

そして、PHPerKaigiをきっかけにPHPStanにコントリビュートできました。

muno-92.hatenablog.com speakerdeck.com

単にコントリビュートできただけではなく、カンファレンスならではのお祭り感もあって最高の思い出になりました。

これ以降だと小さなバグを2件直しただけですが、PHPStanや他のOSSに対してPull Requestを送ったりIssueにコメントしたりするハードルは下がったと思います。

github.com github.com

また、IssueやPull Requestを眺めていて気づいたのですが、日本からPHPStanにコントリビュートしている人が結構いるのは発見でした。

9月

PHPカンファレンス 2022に参加。

muno-92.hatenablog.com

私はオンライン参加でしたが、少しずつオフライン開催のカンファレンスが増えてきたのは嬉しいですね。

11・12月

GitHub Actionsの実行時間を集計するツールをGoで実装しました。

muno-92.hatenablog.com github.com

知的好奇心を満たせたのと、Goを使って1つプログラムを作った事で少しは手に馴染ませられたので満足しています。
一通りの実装は出来たので、来年は他の言語で再実装して比較してみたい所です。

まとめ

今年はPHPStanにコントリビュート出来たのが大きな出来事でした。
おそらく来年もPHPStanのIssueから直せそうなバグを探しPull Requestを送って、としていると思います。
ただ、今は個々のルールクラスに手を入れる所で止まってしまっているので、より深くPHPStanを理解したいなと考えています。

また、英語でコミットメッセージを書いたりGitHub上でやり取りしたりしていると英語力のなさを痛感するので、英語の勉強もしていこうと思います。

と、いうわけで2022年の振り返りでした。

今年一年ありがとうございました。
来年もよろしくお願い致します。

パブリックリポジトリでGitHub Actionsの実行時間を集計する

主にパブリックリポジトリでの使用を目的とした、リポジトリ単位で指定した期間のGitHub Actions実行時間を集計するツールを作成しました。

github.com

GitHub Actionsのアクションとしても公開しており、GitHub Actionsで実行した場合は下記のようにジョブのサマリーとして集計結果を出力します。

これがあると開発が便利になる類のツールではないですが、自分のパブリックリポジトリでどれだけGitHub Actionsを使っているか気になっている方がいらっしゃいましたら使って頂けると嬉しいです。

使い方

詳しくはREADMEに記載していますが、例えばGitHub Actions上で毎月の頭に前月の実行時間を集計する場合、以下のようになります。

name: Calculate usage

on:
  schedule:
    - cron:  '5 0 1 * *'
    
jobs:
  calc-usage:
    runs-on: ubuntu-latest
    steps:
      - name: Set Start Date
        run: echo "START_DATE=$(date -d "$(date +'%Y%m01') 1 month ago" +'%Y-%m-%d')" >> $GITHUB_ENV
      - name: Set End Date
        run: echo "END_DATE=$(date -d "$(date +'%Y%m01') 1 days ago" +'%Y-%m-%d')" >> $GITHUB_ENV
      - uses: muno92/gha-usage@v0.1.0
        with:
          repo: ${{ github.repository }}
          start-date: ${{ env.START_DATE }}
          end-date: ${{ env.END_DATE }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

またコマンドライン版もGitHubのリリースページに添付していますので、手元で動かしたい方はそちらをご利用下さい。

使用する際に注意頂きたい点として、指定した期間に含まれるGitHub Actionsの実行数が1000以下である必要があります。

GitHub REST APIの仕様として指定した検索条件の先頭1000件までしか取得できないようになっているようで、例えば1ページ当たり100件の条件でAPIを叩いても10件目までしか取得できません。
(公式ドキュメントではSearch APIにしかそういった記述が見当たりませんでしたが、他のAPIも同様なのではないかと思われます)

そのため、

curl 'https://api.github.com/repos/muno92/gha-usage/actions/runs?created=2022-11-01..2022-11-30' | jq '.total_count'

のように実行数を確認頂いた上で適切な集計期間を指定して頂く必要があります。

動機

これまで、自作ツールのテストでmatrixビルドを使用したり、Nature RemoのAPIから部屋の気温・湿度を取得するプログラムを定期的にGitHub Actions上で実行したりとGitHub Actionsをプライベートでも日常的に使ってきました。 ただ、パブリックリポジトリでは合計の使用量を確認出来ません。

GitHub Actionsではパブリックリポジトリに対して支払が発生しないので実行時間が分からなくとも困りはしないのですが、自分がどれだけGitHub Actionsを利用しているのか確認してみたかったので、今回のツールを作りました。

仕組み

gha-usageはGET /repos/{owner}/{repo}/actions/runsで指定したリポジトリ・期間に実行されたワークフローの結果を取得した後、各ワークフロー実行結果が保持しているjobs_urlにリクエストを送って実行時間を集計しています。

つまり、必然的にN+1のHTTPリクエストが発生することになります。

出来ればN+1は避けたかったのでまずGitHub GraphQL APIを使って集計できないか調べてみたのですが、2022年11月時点ではジョブの実行時間を取得できません。
(Workflow RunのcreatedAtとupdatedAtの差分を取れば近しい値が取れるかもしれませんが、ランナーのOS毎に実行時間を取得するためにはジョブ単位で実行時間を取得する必要があります。)

GitHub REST APIの場合GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timingというそれらしいAPIはあるのですが、ドキュメントに記載されている通りこのAPIは支払対象となる実行時間しか返しません。

そのため、最初に記載した2種類のAPIを組み合わせて実行時間を集計することにしました。

GET /repos/{owner}/{repo}/actions/runsはレスポンスボディの形式が以下のようになります。

{
  // 指定された条件に合致する(ページ分割される前の)実行結果総数
  "total_count": 65,
  "workflow_runs": [
    {
      "id": 3553437581,
      "name": "Test",
      "node_id": "WFR_kwLOIYWxB87TzSeN",
      "head_branch": "master",
      "head_sha": "6803c43ec8b27c4d8d8a878ee6a18789e623a039",
      "path": ".github/workflows/test.yml",
      省略
      "jobs_url": "https://api.github.com/repos/muno92/gha-usage/actions/runs/3553437581/jobs",
      省略
    },
    2件目以降
  ]
}

そして、workflow_runs1件1件のjobs_urlにリクエストした際のレスポンスボディは以下のようになります。

{
  "total_count": 2,
  "jobs": [
    {
      "id": 9719953258,
      省略
      "status": "completed",
      "conclusion": "success",
      "started_at": "2022-11-26T11:06:03Z",
      "completed_at": "2022-11-26T11:06:46Z",
      "name": "e2e_test",
      "steps": [
        {各ステップのステータスや開始・終了時刻},
      ],
      "check_run_url": "https://api.github.com/repos/muno92/gha-usage/check-runs/9719953258",
      "labels": [
        "ubuntu-latest"
      ],
      "runner_id": 1,
      "runner_name": "Hosted Agent",
      "runner_group_id": 2,
      "runner_group_name": "GitHub Actions"
    },
    2件目以降
  ]
}

各jobのcompleted_atとstarted_atの差分を取ればジョブ毎の実行時間が分かります。

そして、「ubuntu-latest」と出力されているlabelsはドキュメントに

Labels for the workflow job. Specified by the "runs_on" attribute in the action's workflow file.

と説明があるため、(セルフホストランナーを使用していないという前提の上で)この値を見ればどのOSのランナーで実行されたか判別できます。

これらの情報から、

  1. 1ページ目のworkflow_runsを取得
    • 1ページ当たりの件数は最大値の100を指定
  2. total_countが100以上だった場合、2ページ目以降を取得
  3. 各workflowのjobs_urlにリクエストを送り、実行時間を計算
    • 1ページ当たりの件数はworkflow取得時と同じ100を指定
    • ワークフロー毎のジョブ数が100を超えることはほとんど無いだろうと、ここでは2ページ目以降の存在を考慮しない

という流れで集計を行っています。
(2番と3番で投げるHTTPリクエストは非同期)

実装

実装にあたっては

  • HTTPリクエストを大量に送る事になるため、並行処理が強いらしいGoに期待
  • A Tour of GoをやったきりだったGoを手に馴染ませたかった

といった理由でGoを使うことにしました。

実用 Go言語Go言語による並行処理を都度都度参照しながら実装しましたが、並行処理以外ではほとんど詰まることなく進められました。
(パッケージをどう構成したら良いのか悩んだりジェネリクスを使ってみようとしたらメソッドで使えず断念したりといったことはありましたが、どん詰まりする程では無かったかなと思います)

非同期処理を実装する際はいきなり非同期でロジックを組むのではなく、一度同期処理を組んだ後に非同期処理に書き換える形で進めました。
これは一度に多くのことをやろうとするのではなく一歩ずつ進めていくほうが頭が混乱しにくいだろうと考えてのことだったのですが、「同期処理実装時に書いたテストが通る状態を維持しながら速度が上がればOK」とゴールが分かりやすくなったので結果的に良い形で進められたと思います。

そして、ある程度出来上がった段階になって使い方に記載したGitHub REST APIの取得上限が1000件という問題にぶち当たりどうしようかと悩んだのですが、これについてはプログラム側で頑張って対処するのではなく使用者側で適切に期間を指定して貰おうと割り切りました。

まとめ

最終的に、400近いHTTPリクエストが走る場合でも7秒ほどで処理を終えられる実装になったので、Goの並行処理性能にはとても満足しています。
Goだと愚直に実装していくだけだと分かってしまえば実装に迷う事も少なく、言語仕様が少ないシンプルさが好まれるのも分かるような気がしました。
(あまり複雑な事をやっておらずまだGoの辛みに行き当たっていないだけなのかもしれませんが)

また、折角Goで実装したのだからとクロスコンパイルしてコマンドラインでも使えるようにしたのですが、バイナリサイズが5MB前後と小さく収まったのは驚きでした。

他の言語で実装したらどうなるのか気になっているので、(モチベーションが尽きなければ)他言語で書き直して速度やシングルバイナリのサイズ、書き心地を比較してみたいと思います。

PHPカンファレンス2022に参加しました

9月24日(土)、25日(金)に開催されたPHPカンファレンス 2022に参加しました。

phpcon.php.gr.jp

今年はオンライン・オフラインのハイブリッド開催となっており、久しぶりに会場であるPiOに行きたい気持ちもあり悩みましたが、オンラインでの参加にしました。

当日の視聴セッション

今年はPHP本体(php-src)に触れる発表が多かったのが印象に残っています。

とりわけ、実際に機能を提案・実装したりバグ報告したりした経緯を発表されていた

は粘り強く対応されていてただただすごいと思いましたし、PHPはコミュニティで開発してるんだなと改めて実感しました。

それ以外の発表だと、今年業務でパフォーマンスの調査・チューニングをしたこともあり、「Laravel を低速化する技術」は「やっぱそこ効くよね」「そこは効かないのか」「それやっちゃう?w」と楽しく聞けました。

また、「Psalmで"完全に理解した"静的解析」はPsalmの内部に関する情報がみっしりと詰まっていて良かったです。

オンライン参加の感想

今年は昨年までと違い、参加者がコミュニケーションを取る場がtwitter & Discordからtwitter一本に統一されていました。
Discordの方が参加者間でコンテキストを共有して会話しやすい気がしなくもないのですが、どこに投稿しようか迷わずに済むのは良かったです。
(自分がtwitter上での会話にハードルを感じているからなだけで、他の参加者の方はそうでもないのかもしれませんが)

また、質疑応答の時間では運営の方から登壇している方に対して質問の復唱をお願いする場面がありました。
(質問者用のマイクで拾っている声が配信に流れないため)

途中(2日目?)からは質問している方の声も配信に流れるようになっていましたが、オンライン参加者の事も配慮頂いていて大変有り難かったです。

発表自体はたまに音が聞こえにくくなるハプニングが起こりはしましたが、スタッフの方が迅速に対応して下さったおかげでほとんどストレス無く見れました。

ただ、休憩時間にスポンサーブースを回ったり、発表中の会場の空気感を感じたりといったオフラインカンファレンスならではの楽しみは味わえなかったので、現地勢楽しそう・・・とtwitterを眺めていました。

来年は現地参加したいです。

まとめ

今年はハイブリッドでの開催となりスタッフの方は大変だったかと思いますが、2日間技術の話にどっぷり浸かれてとても楽しかったです。
ありがとうございました。

来年は安心して現地参加出来る情勢になっていると良いですが、どうなることやら・・・

PHPerKaigi 2022に当日スタッフとして参加しました

4月9日(土)〜4月11日(月)に開催されたPHPのカンファレンス、PHPerKaigi 2022に当日スタッフとして参加したのでその感想記事です。

当日スタッフとして参加

今回は2020年以来となる当日スタッフとしての参加。

day0の朝、会場のCoconeriホールに着くと「帰ってきたな」と感慨深くなりました。

そして机・椅子を設置したり、バックボード(記者会見でよく見るアレ)を組み立てたりと会場準備を進め、

横倒しにしたバックボードの骨組みにスポンサーロゴが印刷された布を面ファスナーで貼り付けている図。外見から想像するより軽かったです

本編が始まってからの担当は受付&Track B。

2020年に当日スタッフとして参加した際は受付しか担当していなかったのでTrack担当は少し不安でしたが、自動化が進んでいたこともあり問題無く進められました。

具体的に言うと、今回のPHPerKaigiは

  • ノベルティは事前に郵送
  • セッションは基本的に事前録画
  • 質問もDiscordのテキストチャットを使う
  • 幕間の動画があり、次のセッションの案内は不要

となっていたため、受付ではQRコードを読み取って名札を手渡すだけとなり、Trackでは

  • 照明のON/OFF
  • 休憩時間中は扉を開け放ち、次のセッションが始まったら閉じる
  • 各セッション毎に1回、forteeのスタッフ用ページにあるボタンをポチッとする
    (動画切替のため)

をするだけで済みました。

そのため。基本はTrack Bのセッションをじっくり見つつ、たまにTrack Aのセッションを見に行ったりスポンサーブース/アンカンファレンスを覗いてみたりと、カンファレンスを満喫出来ました。

「なんだかアンカンファレンスが盛り上がってるぞ」と見に行ったり、久しぶりに会った人と話したり出来るのはオフラインならではですね。
改めて、今年オフラインで開催・参加出来て良かったなと思います。

Fitbitで計測した歩数 (こうやって可視化してみると、設営・撤収があったday0/day2の歩数が多くなっていて面白い)

PHPerチャレンジ

PHPチャレンジは23764ptで24位

あまりスコアは伸びませんでしたが、デジタルサーカルさんやトラーナさんが作成された問題を解けたのは達成感がありました。

www.dgcircus.com toranabox.com

トラーナさんの5問目には特に苦戦し、会場準備の空き時間にも解き進め、

twitter.com

day0が終わった後にようやく解けました。

twitter.com

問題を作るのは大変だったろうなと感じたので、作問された方には本当に頭が下がります。

ただ、PHPerチャレンジ全体だと、後になって「あ、ここにもトークンあったのか!」と気づいて悔しい思いを何度かしたので、次回はより力を入れてチャレンジしたいです。

まとめ

私は毎年クロージングで主催の長谷川さんが話されている「We are Community」という考え方が大好きです。

昨年はスタッフではなく一般参加と立場を変え、また今年は業務でPHPに触れる機会が減っていましたが、それでもPHPerKaigiという場を用意して頂いたおかげで色々な人とコミュニケーションを取れ、PHPStanにPull Requestを送る機会も得られました。

muno-92.hatenablog.com

「やっぱりPHPPHPコミュニティは好きだし、関わり続けたいな」と実感したので、1年先がどうなっているかは分かりませんが、来年も開催されたら何かしらの形で(今のところの考えでは当日スタッフとして)参加したいと思います。

PHPerKaigi 2022への参加をきっかけにPHPStan本体にPRを出した

PHPerKaigi 2022の翌日、4/12にPHPerKaigi参加者用のDiscordで発生したやり取りをきっかけとしてPHPStan本体にコンストラクタ直接実行を検知するためのPRを出しました。

github.com

PHPerKaigiは実は4日目もあった!?と感じる程の盛り上がりがPRを出すまでにあったので、その一連の流れをまとめます。


2022/4/26 追記
PHPStan 1.6.0がリリースされ、Bleeding EdgeとしてPRで追加したルールも利用可能になりました。

https://github.com/phpstan/phpstan/releases/tag/1.6.0

Bleeding Edgeを有効にし、Levelを2以上にするとコンストラクタを直接呼び出した場合にエラーと見なされるようになります


2022/5/5 追記

PHPStan1.6.6にて、phpstan-strict-rulesにルールが移動されました。

github.com

phpstan-strict-rulesをインストールした上で

includes:
    - vendor/phpstan/phpstan/conf/bleedingEdge.neon
    - vendor/phpstan/phpstan-strict-rules/rules.neon
parameters:
    level: 2

のように設定頂ければ、コンストラクタの直接実行をエラーとして検知するようになります。


大まかな流れ

  • 09:01 t_wadaさんから「静的解析ツールで__construct2回呼び出しを禁止出来ないか」とDiscordに問いかけ
  • 10:18 Psalmでは検知出来る、PHPStanでは検知出来ないと返答
  • 18:02 t_wadaさんから「__constructの明示的な呼び出しをターゲットにするルールが無いのであればコントリビュートのチャンスでは」とコメント頂く
  • 20:15頃 tadsanさん・mpywさんが関心を持つ
  • 20:25 焦り出す自分
  • mpywさんが仮実装を投稿されたり、tadsanさんが対処しなければいけないケースを整理されたり
    • ここでPHPStanの拡張を書くのでは無く本体へのルール追加として提案する方向になる
  • 21:33 phpstan/phpstan-srcをforkして作業に取り掛かり始める
  • テストクラス・ルールクラスの実装を進め、その間にもDiscordで実装方法が検討される
  • 22:37 下記2パターンを検知出来るようになる
    • 同クラスのコンストラクタ以外のメソッドから$this->__construct
    • $生成したインスタンス->__construct
  • 23:08 コンストラクタ内で$this->__constructを実行する再帰呼び出しのパターンはエラーにならないようにする
  • 23:38 __constructの静的呼び出しを検知出来るようになる
  • 23:52 tadsanさんがIssueを立てて下さる
  • 00:13 条件式の整理などリファクタリングを行い、PRを提出
  • 00:32 MethodCall(->で呼び出すパターン)とStaticCallをそれぞれ別のルールでチェックする形に変更
    (その方が良いとIssueにコメントがあったため)
  • CIでLint/静的解析/テストが落ちる
  • 01:15 Lint/静的解析/テストを通るように修正
  • 01:25 CIの全ジョブがグリーンになり、Discord上の面々は解散
    記念スクショ
  • 03:52 PHPStan作者のOndreさんにPRが承認され、後は引き継いで頂けるとコメント頂く

発端

PHPerKaigi day1(4/10)でt_wadaさんが発表された「予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント」にて、たとえDateTimeImmutableであっても生成したインスタンス__constructを実行する事で値を書き換えられてしまうとの話がありました。

この__constructが2回実行されるコードについて、

静的解析で禁止すれば良いと発表資料に入れたいが、そういったルールは無いか

とt_wadaさんがDiscordで質問されたのが発端となり今回のPRが生まれました。

4/12時点の静的解析ツールでの検知

t_wadaさんの質問を見て、最初は「PHPStanのphpstan-disallowed-calls拡張を使えば禁止出来るのでは?」と思いました。
しかし、phpstan-disallowed-callsで特定クラスのコンストラクタを指定した場合、new DateTimeImmutableもルールに抵触してしまいます。

To disallow naive object creation (new ClassName() or new $classname), disallow NameSpace\ClassName::__construct in disallowedMethodCalls. Works even when there's no constructor defined in that class.

PHPStan自体のレベルを9(MAX)に設定しても__construct直接実行は検出出来ませんでしたが、Psalmでレベル1(MAX)にした場合はエラー検知出来ると分かったためその旨をDiscordに投稿しました。

※Psalmも下記の理由でエラー検知出来るだけで、__construct直接実行を禁止するルールがあったり、__constructを直接実行してはいけないとエラーメッセージが出たりする訳ではありません

  1. $dt->construct('2022-04-10')の場合
    https://psalm.dev/docs/running_psalm/issues/UnusedMethodCall/
    に抵触する
  2. $hoge = $dt->construct('2022-04-10')の場合(1番を回避出来てしまわないかの確認)
    __constructの戻り値はvoidのため、
    https://psalm.dev/docs/running_psalm/issues/AssignmentToVoid/
    に抵触する

PRを出すまで

上記の回答に対してt_wadaさんからコントリビュートチャンス!とコメント頂きましたがこの時点では腰が引けていました。

自分用のツールを「誰も見てないだろ」位の気軽さでパブリックリポジトリとして公開してはいるものの、OSSへのPRといったらPHPマニュアルに出した事が1回あるだけで、まだまだハードルを感じてしまっていました。

そういった気持ちで「とはいえ現状該当コードを禁止するルールは無い気がするし、Packagistでダウンロード数が多いPHPStan拡張をざっと見てみてそれでも無かったら作るかなあ」と考えながらDiscordを見たらtadsanさん・mpywさんが実装に関心を持たれており、速く実装しなければ、と慌てて取り掛かり始めました。

そして名前やテストの書き方どうしようなどと考えている間にもDiscordでは実装方法の検討などが進み、話がまとまった後、実装は私に任せて頂ける事になりました。
(ここで「自分がやる」ではなく「実装任せた」と言って頂けて大変有り難かったなと感じています)

過去にPHPStanの拡張を作ろうとした事があり、なんとなく「ソースコードPHP Parserで解析した結果から良い感じにNodeを選び、__constructという文字列が含まれているかチェックすればいい」位のイメージはついていたのですが、具体的にどのように実装すれば今回のケースを満たせるかは調べてみないと分からないといった状態だったので、アドバイスのおかげでその辺り悩まず、実装に集中する事が出来ました。

また、PHPをがっつり書くのは久しぶりだったのですが、抽象クラス・インターフェースを指定してクラスを定義するだけでどのメソッドが必要か型情報から教えて貰え、PhpStormなら1クリックでそのメソッドを生やせるため「PHP/PhpStorm最高だな」と改めて感じました。

そして実装を始めてからも

  • __constructを呼び出すのはこういったケースもある
  • この静的解析のエラーはここが原因

など、色々と助けて頂きました。

焦ってローカルで全テストなどを走らずにpushしたことでPR出してからCIを通すまでに手間取ってしまったので、その点は反省点です。

(ちなみに、phpstan-srcのMakefileに定義されているタスクの内、tests-coverageを実行するとM1 ProのMacでもファンがぶん回り、M1でもファンが回る事あるんだとなりました。)

さいごに

このような流れで、PHPStanへのPRを出す事が出来ました。

自分1人ではそもそもPR出す踏ん切りがつかなかったり、実装にあたっての不明点解消に時間が掛かったりとここまでスムーズには進められなかったと思います。
背中を押して頂いたり、実装案を提示して下さったり、色々と助けて頂いたt_wadaさん、tadsan、mpywさん、yu-ichiroさん、ありがとうございました。
そして、他の人が今回の自分のような立場に立っていた時に、上手く助けになれたらと思います。

また、カンファレンス会場のアンカンファレンスでスクリーンを囲んでわいわい話しながら実装しているような熱を感じ、単純に作業していて楽しかったです。
こういった偶然の出会い・化学反応がカンファレンスの醍醐味であり、たまらない点なのかなと改めて感じました。

これをきっかけにPHPStanへの関わりを(1ユーザーとしても1開発者としても)深めていきたいです。