yieldとコルーチンと非同期処理

1人AdventのDay-15です。もう2週間経ったのか、12月よ・・

adventar.org

最近、ちらちらっと「Swoole」という単語を聞くようになった気がしており、それについて調べてみよう〜という予定でした。が、「まずコルーチンとかについて頭の中を整理したいな・・」と思ったので、このようなタイトルになっております。

きっかけ

Swooleについて初めて意識したのが、TLにこのツイートが流れてきたときかなあーと思います・・

Swooleってなんなのさ

BEAR.Sundayのサイトに載っている説明はシンプルにまとまっていました。

SwooleとはC/C++で書かれたPHP拡張の1つで、イベント駆動の非同期&コルーチンベースの並行処理ネットワーキング通信エンジンです。 Swooleを使ってコマンドラインから直接BEAR.Sundayウエブアプリケーションを実行することができます。パフォーマンスが大幅に向上します。

bearsunday.github.io

ググったら「副題: 最近Swooleが楽しい話」というUzullaさんの記事が出てきました。

最後まで読んでみて、いろいろと「なるほど」がありました。結構ボリュームがありますね。 スライドを見ていたら、サイトも出てきました。

www.swoole.co.uk

「プロダクション品質の非同期PHPプログラミングフレームワーク」ふむ。

・・・なんだか「こんにちは、闇の方から来ました。私、色々できます」みたいな凄みを感じます。

Sooleと非同期プログラミング

私が「Swoole気になるな」と思ったのは、「非同期」があるの?と思ったのが強いきっかけの1つです。
非同期、よいですね、ふふふ。

先のuzullaさんのスライドを見ると「Swoole、めっちゃ機能多そうだな」という感想をいだきます。何か新しい仕組みや概念を理解しようとするときに、私は「なにか1つポイントを見つけて、それを軸としてイメージを具体化していく 」というアプローチを好みます。それをしながら、「 必要になったら時点で他の筋に移動したり、行ったり戻ったりする」を繰り返して、全体像を掴んでいこうという感じです。

多機能だからこの記事で全部触れるの無理よな〜っていう思いもあり、まず「非同期プログラミング」をキーワードとして調べている内に、主題が入れ替わった形です。

PHPとコルーチン

先のスライドの中にも、「コルーチン」が出てきますね。

[f:id:o0h:20181216010344p:plain f:id:o0h:20181216010434p:plain

PHPでの非同期処理を得意にさせるrecoilなどにもcoroutineについて言及があります。

では、コルーチンとは一体なにで、どうしてそれが非同期処理と関係があるのでしょう?

そもそも「コルーチン」がなにか、という話をつまみ食いしておきます。

まずは、例によってWikipediaです。

コルーチンはいったん処理を中断した後、続きから処理を再開できる

とか

接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。

(https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)

なんて書かれています。
「継続」「中断」「再開」という言葉が出てきました。これらを意識することができれば、意味がつかみやすくなるのかもしれません。

継続

継続については、こちらの記事が良かった。
なんでも継続

「意識することができれば」と書きましたが、「今までもそこにあったのに、見えてなかった/認識することができなかった対象を、ふと「当たり前」に存在を感じられるようになれば」ということで、この書き方をしました。そして、この「なんでも継続」の記事が、正にそのスタンスで書かれていて良かったな〜と。。

すんごいザックリといって「なんかの処理をしていて、途中でどっか行って、戻ってきたらその 続き から処理を行う」というのを強調するために「継続」という概念が必要な気がします。「どっか行って」=中断、「戻ってきて」=再開。

関連: 制御構造 - Wikipedia

yield

さて、PHPにおいても「中断」「再開」といった単語を(サブルーチンのそれとは違い)強く意識する場面があるのではないでしょうか。
イテレータ、ジェネレータ、yieldといったキーワードを思い出してください。

ジェネレータ(Generator)とは、イテレータコンパチなインターフェイスを持つけど、 指すべきコレクションがあるわけでもなく、そのたんびに 値を作り出して返すようなモノを作るモノをいいます。

ジェネレータ と コルーチン - みねこあ

PHPはyieldによってジェネレータを作成できます。

  • 中断:
    • ルーチンの内部の任意の箇所で、(呼び出し側に値を返して)処理を終わらせ
  • 再開:
    • 「さっきやったとこ」の続きから処理を始める

という内容を備えています。「一旦処理を中断した後、続きから処理を再開できる」ということであれば、コルーチンの定義に当てはまると言って良いのでしょうか?

ジェネレータ/コルーチン

yield(PHP)はジェネレータ関数を作るのだから、じゃあyeildを使えばコルーチン?コルーチン = ジェネレータ?と思い、今度は「ジェネレータ」についてWikipediaでみてみます。

ジェネレータの実装としてはコルーチンやcall/ccやマルチスレッドを使う方法が考えられる。

ジェネレータ (プログラミング) - Wikipedia)

ということで、どうも一致する概念ではないようです。
どういう差なのかな、と調べてみたらPythonに関しての例を説明している記事に行き当たりました。

yieldした後にコントロール出来るという点がジェネレータと違うようです。

Pythonのyield(コルーチン編) | KISO-REN

この「コントロール可能か否か」というところで、コルーチンは分類されるぞ!というような事をStackoverlowの問答で発見しました。

In the asymmetric coroutine model, "asymmetry" refers to the fact that there is a stack-like caller–callee relationship between coroutines. A coroutine can either call another coroutine or suspend itself by yielding control to its caller, typically also yielding a value to the caller at the same time.

What is the difference between asymmetric and symmetric coroutines? - Stack Overflow

ジェネレータを含む「非対称コルーチン」においては、「一旦処理に入ったら、そのまま、最後まで内部生成された値を返す」ということになり、これを「一方的(= callerには制御権がなく、コルーチンにのみ自身の制御権がある)」と言っている・・のでしょうか?そんな感じと理解しました。

そしてさらに、Pythonの例を見つけました。

魅力的なPython: ジェネレーターによるステート・マシン

かなり前のバージョンにおける言及をしている記事で、この中では「(Python2.2で導入された)yieldでのジェネレータは半コルーチンで、本当のコルーチンを実装したよ」とされています。

内容を見てみると、「ジェネレータの中から別のジェネレータを呼ぶ事は可能」というのを応用して、「ジェネレーター同士がお互いを呼び合う」ようにしている・・というのがポイントでしょうか。

先の「Pythonのyield(コルーチン編) | KISO-REN」の記事で言及されているPython Coroutineの元ネタPEPを見てみると、次のように書かれています。

However, if it were possible to pass values or exceptions into a generator at the point where it was suspended, a simple co-routine scheduler or trampoline function would let coroutines call each other without blocking -- a tremendous boon for asynchronous applications.

PEP 342 -- Coroutines via Enhanced Generators | Python.org

ということで、IBMの記事にて実装されている内容も今のPythonならシュッとかけるのかもしれません。
これ以上は、今回のエントリーの本題から外れていきそうなので話を戻します!

PHPにも同様の機能が導入されています。

qiita.com

yieldと非同期処理

ここまで見てきたとおりで、コルーチンは「別の何かをする処理」であると言えます。そして、「中断して(=処理を呼び出し元に戻して)」「再開して(=自身の中に状態を維持・継続ができる)」というのがポイントです。
ということは、「コルーチン内部の処理を行っている最中に、呼び出し元の処理をブロックしない」もしくは「取り急ぎ呼び出し元にコントロールを戻しつつ、自分の処理もちょいちょい進めておく」事が可能なら、実質的に非同期処理が可能という着想で合っていますか。

まさに、そのような内容を扱っている資料が上がっていました。

これは、以下の条件によって実現されているものと思います

  • non-blocking APIを備えた実行内容がある
    • curl_multi
  • coroutineをstackさせておく
  • stackされたリクエストの解決まで、無制限のループ処理によって状態のチェックを行う

そうすることで、「すべての解決を待つ」ことをしつつ「個別の処理は並列的に行う」という操作が可能になったという理解をしています。

「PHPでコルーチンを利用することはできるが、それ自体は「あらゆるものをノンブロッキングにする」ような能力はなく、あくまで並列化の可否は処理内容としてノンブロッキングAPIを利用出来るか次第で、また「整いました!」とイベントを拾いに行くのは自分で監視をじっそうしなければならない・・・」 こんなところでしょうか。

ReactはイベントループとノンブロッキングI/Oの実現を目指した、ループ機構です

感想

「なんでもすぐに並列化できちゃうすごいやつ!」というのは、PHPという言語上は存在しないんだなーというのを改めて認識しました。
その一方で、ノンブロッキングな処理が実装されている領域についてはPHP上で実装が可能そうにも思います。例えばMySQLからのデータ取得などは、個人的な関心からいっても恰好のターゲットです。
実際に手を動かしてみないとイメージ湧かないな〜とも感じたので、「引き続き模索してみたいな」と思っています。

qiita.com

その他の文献

本文中では触れなかったものの参考にした記事です

個人的にPHPのassert()の使い方を整理する

1人AdventのDay-12です。12ですかぁ〜

adventar.org

PHP*1にはassertというものがあります。

www.infiniteloop.co.jp

これ便利だし、コードを「良くする」ことのできる嬉しい機能だと思っています。
ただ、往々にして「開発を健全に続けていく」という目的の下にあっては、「曖昧さ」「ゆるさ」は罪になることもあります。そんなつもりはないのに・・・

ここでいう「曖昧さ」というのは、「自分以外の、他の人が、同じようなことを書いたり書こうとした時に、同じ感じでかけるか?」という、悩ましさの大きさだと思ってください。
プログラミングをやっていて、醍醐味で貼りますが、こういう風に動けばいいな!って思いついてから「あ〜どうやって書こう、何が正解かな?」って考えさせられる部分というのは、苦しみを招きます。

処方箋として。
「やり方を決める」っていうのは、1つの手だと思います。「これはこういう時に」「こういう風に使いましょう、使って良い」っていうガイドラインがあれば、気持ちが軽くなりますね。

さて、話を戻して「assert」や・・・いつなら使っていいんだ・・・

実際に業務のコードにチラホラと織り交ぜて見ているのですが、自分なりに「ここはOK、これは微妙」っていうラインを形にしてみようと思いました。
今日はそういうブログです。

前提として

  • assertは、プロダクション環境では使わない。実行されるのは、開発環境においてのみである
  • すなわち、実行時にデータを守ったり、ユーザーを守るためのものではない
  • 要するに、「開発時に実装のミスをしないように」、「開発者を支援するため」のもの

と思っています。

「実行されるコメント」という感覚。

私の場合

「assertは使って良いもの」としつつ、先述の「曖昧になっていく」のを恐れています。そのため、限定的な使い方をしています。
とりわけ、 LogicException とはなぁ。。。という風に感じています。

かなり探り探りでありつつ、「このくらいなら・・・」というのを書き連ねてみます。

privateメソッド内での利用

assertを「実装ミスをあぶり出す」という目的で使うとなると、「どういう使われ方をしそうか」というのは限定して考える必要があります。

その点、privateメソッドに関しては「まぁ、うっかりミスは少ないかな・・」という自信を持ちやすいかと思います。
protected, publicだと、どう使われるか保証できるとは言い切れないので、LogicExceptionをしっかりつかうかもしれません。

「ここに来る前に、このメソッドを実行されているか」みたいなチェックなど。インスタンスメンバーの設定状況だったり、DIの注入状況だったり・・・といったケースでしょうか。

自作トレイト内での呼び出し元クラス設定状況

インターフェイスや抽象クラスでは「メソッドの定義を強制できる」と思うのですが、クラス定数・クラスメンバーについてはどうでしょう。 「このトレイトを使うには、 $this->config に arrayをいれてある」とかは、「実相時点で拾える必要がある」と思います。

そういった意味で、assert()を安全に使えるのではないでしょうか。

テスト中の前提条件

これを結構よく使っているかもしれません。

テストなんて「検査」しかしなくない?という話もあると思います。
たとえば、「ユーザー削除処理を行うAPIの実行時に、ユーザーの持っている投稿をすべて削除する」など。

この場合、「このユーザーIDに紐付いている投稿が0件になっている」ことをテストケースに絡めることが可能です。
が、それを実行する場合、利用するフィクスチャデータに「そもそも投稿がなかった」という状況だと、「検査は通るけどテストの意味を持たない」ことになってしまいます。
とはいえ・・・「もともと、なにか投稿していたよね?」という確認は、このテストケースにおいて「やりたいこと」ではないわけです。

そうした場合、「やりたいこと」と「守らないといけない前提」を明示的に区別するために、PHPUnitのconstraintとassertを併用するのはありかな〜と思います。

まとめ

もう少し使ってもいいんじゃないかな〜活躍してくれ!!という気持ちはあります。
その一方で、別にどんな機能も「無理に使う」必要はないと思います。
それでいて、「どっちでも良い部分」という根拠の説明がふわっとしやすい部分は辛いのです。とりわけ、チーム開発を行っていると、「なんとなくこう!」というのは、レビュイーに対して出来ることではありません。でも許容していると、「緩さ」が蔓延していき、より辛く・・・・誰も幸せになれないわけです。

なので、「チームとしての基準を決める、合意をとっていく」「アイディアや思考プロセスも含めて、プラクティスを蓄積していく」ことが重要なのかな〜と思っています!

*1:ここで言っているのは7.xですね

PhpStormでのGit/GitHub利用

1人AdventのDay-11です。

adventar.org

今日は、すでに10,000,000回くらい言及されていると思いますが、今年ちゃんと使い始めたかも!そしてすっげ、便利!!系のネタで、「PhpStormからGit/GitHubを利用する」をしたいと思います。
この辺りを気にしなくていいのが自分のブログの醍醐味じゃろ、っていうノリです。

なぜ「エディタ」でそこまで

1つに、「ウィンドウを行き来する必要が減る」というのは、やっぱりでかいです。
IntelliJ IDEA含め、世のIDEは「ショートカットを覚えたもん勝ち」「いかに思考より早く必要な機能を起動できるか、体に馴染ませるか」みたいなところがあると思っています。

その中にあっての、Git。便利です。
あと、こまめにコミットしておくと安心感がすごいんですよね。PR飛ばす前にrebaseをかけることで、コミットにもちゃんと意味をもたせる〜観点で整えよう!というくらいの木基で、本当はバシバシとコミットしていくのが良いのかも・・・というくらいに思っています。

commitをする

⌘+k です。

(大いに、私の好みの問題ではあるのですが!)
「コミット前に整える」機能がstormの力を遺憾なく発揮する形で充実しており、そして「Amend commitがすぐにできる」というのが嬉しいのです。 f:id:o0h:20181212022121p:plain

何が嬉しいのか?
とりあえず、一旦wipでコミット が、非常にやりやすいな〜と・・

Reformat codeや Optimze importsというのは、「どっか弄ったときに、直し忘れてた」みたいなのを消してくれます。 useされているけど使っていないよ とか 50音順に並んでいないよ とか、そういう。
この辺りは、当然「レビュアーに失礼のないように整えましょ」という話ではあるのですが、ここまでくると、「通常作業の自然流れの中に『細かいコミット』を組み込んで見ようかな?」という気にもなります。

commit messageを入力したら、 ^+k でコミットを実行してください

pushする

⌘+⇧+k です。

f:id:o0h:20181212022920p:plain

pushは、commitほど「細々と実行する」ようなメリットはないと思います。
ここで便利なのは、「pushする前に気づいちゃったことをさくっと開く」のが億劫でない、やはり「エディタとの距離が近い」ことによる恩恵があるな〜といった感じです

checkout

f:id:o0h:20181212023328p:plain f:id:o0h:20181212023254p:plain f:id:o0h:20181212023413p:plain

clickだけで、すすす〜っとブランチを移動することが可能に。
嬉しくないですか?

view pull requests

reviewするときに、「このブランチをじゃあ実際に落としてきて」って絶対にやるじゃないですか。

f:id:o0h:20181212024440p:plain

f:id:o0h:20181212024842p:plain

左カラムがPRの一覧です。
各PRを右クリックすると、「Create new local branch」「Open on GitHub」というメニューが現れます。すごい。

find pull request

「この行の変更は、どういう経緯だ・・・?」ってなるときに使います。

f:id:o0h:20181212025138p:plain エディタ上の、気になる箇所を右クリック→コンテクストメニューから「Find Pull Request」です。 ブラウザ上で該当のPRが表示されます。

annotate

コードの行番号を右クリックすると、「annotate」というメニューがあります。

f:id:o0h:20181212025350p:plain

これで簡単にblameができて、大助かりです。
authorの表示されている部分にhoverすると、コミットメッセージが表示されます。

f:id:o0h:20181212025538p:plain

また、クリックするとコミットの詳細が表示されます

まとめ

今の所、私に「馴染んでいる」という感覚のある機能はこのくらいです。
まだまだ知らない機能、使えていない機能がある・・・・・・というのは大いに自覚しております。探していきたいな、という所存。

CakePHPを3.7に上げてみたのでその時の作業のポイントをまとめる

1人AdventのDay-10です。

adventar.org

日本時間の9日、CakePHPの3.7のstableがリリースされました。

bakery.cakephp.org

リリースノートや移行ガイド、 GitHubの3.7ラベルがついているIssue/PRを眺めながら、主観&自分に関係しそうな箇所をまとめたのが昨日のアドカレ記事でした。

cake.nichiyoubi.land

今日は月曜日〜ということで、会社に出社したのですが、モノは試しということで社内ツールのCakeアップグレードに手を出してみました*1

結構スムーズに行ったのかな?と思うので、その際の手順などをまとめてみたいと思います。

前提・ゴール

まず、移行対象とするアプリケーションの状況や今回の作業のゴールをまとめます。

アプリケーションの内容・立ち位置

  • 社内向けの管理ツールです
  • もともとCakePHPは最新版(の1個手前!)である3.6.13までアップデート済み
    • 3.6.xならウィークリーで更新PRが作成されるようにしてあり、原則として最新版に追従させる
    • 3.5/3.6で追加されていたdeprecatedも全て推奨される方式に移行済み
  • サーバーサイドはAPIのみを提供 。なので、View/Form周りはほぼ使っておらず
    • このレイヤーを利用しているアプリケーションにおいては、大まかな作業の進め方は変わらないものの、手間は確実に増えると思います
  • モデルの単体テスト、コントローラの結合テストは原則として必須にしているので、割と機械的なチェックを信用した移行ができる

APIアプリケーションという限定的な機能に留めていること、テストや小まめにバージョンアップしているなど「気軽にアップデートしても大きな作業になりにくい」という状況は整えられていると思います。

今回のバージョンアップ作業のゴール

  • (当然ながら)デグレはさせない
    • 単体テストを完走させる
    • コード解析(PHPStan)も完全に通るようにする
    • 作業後、主要機能を中心に手動でリグレッションテストをかける
  • 新機能の利用などは都度
    • 「普通に動く」を一旦ゴールに

事前準備

変更内容の把握・見立て

何よりも、まず最初にチェックすべきはやはり移行ガイドです

3.7 Migration Guide - 3.7

deprecatedをみる

3.6に引き続き、今回のバージョンの主な意図としては「4に連れて行かない機能に廃止予告をする」ことにあると思います。
その内容は、(またしても)setter/getterの分離が目立ちます。

後述しますが、ここで「変更されるものすべて」を厳密に見る必要はないと思います。特に、メソッドの分離系は「いつものですね!」といって流してしまっても良いかもしれません。
どちらかといえば、私の場合、「自分のアプリケーションで関連しそうなクラスは、どのくらい対象になっているかな?」という観点でチェックをしました。これが多ければ作業は重くなると予想できますし、そうでないならサクッと行けそうです。
「今やっちゃうか後で腰を据えてやるか」を占うために、大体の判断を「どんなクラスがあるか」で見たわけです。

behavior changeをみる

(少なくとも今回の変更においては)「まっとうに使っていてもヤバそう」という内容の破壊的な変更はないように感じました。
しかしながら、アプリケーションに埋め込まれた地雷が発火する可能性もあり、やはり1番ケアするべきはこの項目だと思います。

意図が読み取れない・影響範囲のイメージがパッと出てこない・・というものに関しては、対象PRを探し出して読んでいってしまったほうが、安心して移行作業に取り組めるかもしれません

new featuresをみる

この節は、「とりあえず動くように移行する」という観点で言えば、無関係といえば無関係です。ただ、折角なのでこの機会に読んでみてしまったら?くらいの気持ちで目を通しています。
内容によっては、behavior change/deprecatedになった「背景」や、あるいは「どの方向に進もうとしているのか」という設計者の思想が感じ取りやすくなる場合もあるでしょう。

作業の実施

ここから実際の作業に入ります。

ごく個別的なケースになりますが、(API専門という)小さなアプリケーションですら「踏んだ穴」となれば、一般的なアプリケーションにおいても同様な作業が発生する可能性も低くないと思います。
そういう意味で、「実際に対応した内容」も併せて紹介していきたいと思います

静的解析

IDE・PHPStan(Phan)を利用します。
今回はPhpStormだけで賄えた部分だけ対応し、PHPStanは別箇所での反応がありませんでした。なのでPhpStromの活用に話を絞ります。

  • 検出する
    1. Code -> Run inspection by name 「Deprecated」
    2. 検査対象を指定し、OK
      1. なお、このときに利用するために予め「Custom scope」を定義しておくと便利。私は、AppApp\Test を定義してあります

移行作業に静的解析での結果を利用する目的は、クラス・メソッド単位で宣言されたdeprecatedへの対応です。

これはPhpStormで洗い出して潰します

IntegrationTestCase

コントローラー及びシェルのテストに主に利用していた、(Console)IntegrationTestCaseがdeprecatedに仲間入りです*2

  1. 継承元をすべて \Cake\TestSuite\TestCase へ変更する
  2. Cake\TestSuite\IntegrationTestTrait をuseする

Runtimeでのdeprecated対応

実行時に、引数やコンテキストによって deprecationWarning() を発火する部分への対応です。

  1. 検出する
    1. これは完全にテストに頼りました。なので、カバーできていない部分は拾えていない・・・逆を言えば「諦めてもいい」のかな、とも思っています

ちらっと蛇足。
ありがたいことに「廃止」ではなく「deprecated」です。つまり「なくなる」訳でもなければ「まぁ別に普通に動きはする」というものです。
どのように最新版対応を進めるか?というやり方次第ですが、大雑把に言えば「もし、見落としがあって対応がとれていなくても、死にはしない」「だから、動かしてみて、warningを感知したらその都度で事後対応を行っていく」というのも1つの上手いやり方のようにも思います。

Plugin::load()

3.5から入った新しいPluginの仕組みを使うように促しています。

  1. (bootstrap.phpから) Plugin::load()を消す
  2. Application.php 上で、 $this->addPlugin()によりプラグインを読みこむ

Email::setConfigTransport()

これもcreate-projectした時からの付き合いだと思います。
私の場合、Email周りは利用していなかったので設定ごと削除しました。
・・・どこかで参照が走ってエラーとなる可能性もありますので、その時に対応すればいいかな、くらいに捉えています。

Plugin::loaded()

今回、これが1番悩んでいるのですが・・・ bakeプラグインが依存している、twig-viewのbootstrap.php内での利用です。
masterにおいては修正が加わっているのですが、まだリリースされていません。

github.com

影響がでるのが「debugが有効」なケースでのみに限定されそうなので、今のところはスルーしています・・

Dispatcher is deprecated.

こちらは厳密には以前のバージョンで入ったDeprecatedですが、今回のIntegrationTestCaseの対応と絡んで引っかかるようになりました。

IntegrationTestがコントローラーアクションをエミュレートする際のcalliingクラスには2つあります。 MiddlewareDispatcherLegacyRequestDispatcherです。
このいずれを使うか?は、IntegrationTestCase/IntegrationTestTraitの setUp() メソッドにて設定が行われています*3

しかしながら、静的解析の時点では「継承元を変えてトレイトを食わせる」という作業を行ったのみであるため、テストクラス自体がsetUp() メソッドを持っている場合は IntegrationTestTrait::setUp() を呼び出すようになっていませんでした。
そのため、正しくフラグがセットされず、LegacyRequestDispatcher が起動される・・という流れになってしまっていたわけです。その先で、deprecatedされている Dispatcherクラスが使役されます。

以下のようにしました。

<?php
//in some controlelr test case 

use IntegrationTestTrait { setUp as integrationSetUp; }

public function setUp()
{
    $this->integrationSetUp();
}

内部的に parent::setUp() も呼ばれているので、既存の実装が破壊されるようなことはないと思います。

Declaring fixtures in underscored format in TestCase::$fixtures is deprecated.

これが1番手間がかかりました!w

利用するFixtureの宣言を、CamelCaseで行うように変更されています。
そのため、これまで $fixtures = ['app.article_categories']; のようにsnake_caseで宣言していた箇所が警告されるようになりました。
・・・fixtureを使うテストクラス全部。。。

PJ全体検索で $fixures = などとして地道に変更を行っていったのですが、Inflectorユーティリティなどと絡めて自動書き換えもできたのかな・・・?という気もしています。
このあたりは、テストのボリュームとの兼ね合いで。

まとめ

バージョン移行系の作業は、手間もストレスもかかりますが、やはりそれ相応のメリットもあるのかなと考えています。
セキュリティリスク含め、バグを含む可能性も時にはありますが、少なくとも定期的な更新(最新化である必要は必ずしもない)はやるべきだ、と思います。

その時の肝が、

  • 大幅アップデートにならないようにこまめに更新を行っておく
  • 安心感を担保するに足る十分量・幅の自動テストを用意しておく

の2つだな、と改めて感じました。

引き続きCakePHP4を待ちながら、また今回入った新しい機能をワクワクしながら使っていきたいなー!と思っています

*1:まだレビュー待ちのステータス

*2:なお、今回のケースに置いてはシェルコマンドの実装がないので、ControllerTestCaseのみの対処で完了しました

*3:正確には、ディスパッチャの選択に関わるフラグのセットをここで行う https://github.com/cakephp/cakephp/pull/12072/files#diff-30ad814a76cb9a0911ed10f377ace431L181

GItHub Actionsを理解しよう!

Beta参加の秘密保持条項に反しているのでは?という指摘をいただいたので、一部修正を行いました

1人AdventのDay-7です。なるほど、1週間・・・

adventar.org

私の環境でも、GitHub Actionsを触れるようになりました!!
自分の手で実際に触ってみて、動かしてみると「おお!これはすごい!!」という感想です。。。!

実際に動いているものを見ないと「何があってどういうものなのか」のイメージが付きにくかったのですが、手元で動かしてみると「なるほど、、、そういう!」とワクワクしてきます。
今の自分なりの理解を、改めて、ドキュメント等を整理する形でまとめてみたいと思います。

GitHub Actions、ざっくりいうと

  • GitHub上での様々なイベントで発火させる「Workflow」と、Workflowから具体的な起動される「Action」からなる
  • Actionは、利用するDockerイメージを指定して、そのコンテナの内部で実行される
  • 1つのWorkflowに、複数のActionを連鎖・並行させて紐付けることができる
  • Workflowの編集のためのvisual editorが用意されていたり各Actionごとの実行結果(ログ)の閲覧が可能

個人的な感想としては、

  • 今まで1つの汎用CIにまとめていた処理を、細分化・専門化して組み合わせることが用意になりそう
  • 自前のDockerfileをゴニョゴニョして使い放題なので、ローカルでやっていた処理を簡単にクラウドに移せそう
  • 通常のGitHub(git操作やIssue更新など)だけでなく、incoming hook系のAPIも用意されているので、chatops等の実装とも親和性が抜群に高そう

といったところです。
現在、業務でCIの活用見直しやデプロイフローの最適化を進めている最中です。それと絡めて、「Travis CIの処理をいい感じにする」というネタで触ってみました!


やろうとしていること

  • 今の状態
    • Travis CIを利用しているプロジェクトにおいて
    • script/deploy等をベタで書いており
    • 例えば「tagをpushした」際にも丸々とscriptフェーズが実行される
  • やってみたいこと
    • master以外のブランチへのpush/prは、scriptまで。deployはしない
    • masterの修正(merge commit)は、scriptとdeployを行う
    • コードに差分がないときにはdeplyフェーズのみを実行し、scriptを実行させない
      • リリースの作成やタグ付けなど!

これを、「.travis.ymlの修正を行わずにできるかな」という風に遊んでみたいと思います。

サンプルプロジェクトのセットアップ

実際に遊んで見るに当たり、サンプルとなるプロジェクトを用意しました。CakePHPのスケルトンプロジェクトを設置し、ごくごく簡単なtravis.ymlを一緒に置きます。

$ tree -a -L 3 -I .git
.
├── .travis.yml
├── README.md
└── o0h_home
    ├── .gitignore
    └── app
        ├── .editorconfig
        ├── .gitattributes
        ├── .github
        ├── .gitignore
        ├── .htaccess
        ├── .travis.yml
        ├── README.md
        ├── bin
        ├── composer.json
        ├── composer.lock
        ├── config
        ├── index.php
        ├── logs
        ├── phpcs.xml
        ├── phpunit.xml.dist
        ├── plugins
        ├── src
        ├── tests
        ├── tmp
        ├── vendor
        └── webroot

.travis.yml

language: php

php:
  - 7.2

cache:
  composer: true
  directories:
    - o0h_home/app/vendor

install:
  - cd o0h_home/app && composer install && cd $TRAVIS_BUILD_DIR

before_script:
  - echo "I'm in before_script!"

script:
  - cd o0h_home/app && vendor/bin/phpcs

deploy:
  - provider: script
    script: echo "I'm in deploy @master!"
    on:
      branch: master
  - provider: script
    script: echo "I'm in deploy @tag!"
    on:
      tags: true

まずはTravis CIのAPIを、最低限理解する

APIがどんな感じになっていれば行けそうかな?を整理する

Actionsは「どんな条件で」「どんな風に」処理を呼び出すか?という機能に見えます。
今回、私が求めている世界は、「.travis.ymlに記述されている内容を部分的に実行する」という術が必要なのではないでしょうか。つまり、「こういう呼び出し方をしたら、scriptをスキップできるよ」という方法があれば勝利に1歩近づきます。

そのためには、

  • A: 実行対象となるフェーズを任意に組み合わせて、ビルドを実行できる
  • B: 実行内容をAPIキックのタイミングで任意に改変して、ビルドを実行できる
  • C: 予め実行内容(のセット)を定義しておき、ビルドを実行できる

このいずれかの手立てがあれば良さそうです。*1

ということで、「Travis CI側のApiがとうなってるの?」を掴まない限りは、何も始まりません。

実際にAPIを見てみる

Triggering builds with API V3 - Travis CI

こちらを覗くと、

  • ビルド作成のリクエスト時に、.travis.ymlのコンテンツと同じような処理内容を渡すことができる
  • もともとの.travis.ymlの内容と、リクエストに含めた処理内容を、「どう混ぜるか」を指定することができる(merge_mode)
  • merge_modeは以下の3種類
    • replace replaces the full, original build configuration with the configuration in the API request body
    • merge (default) replaces sections in the original configuration with the configuration in the API request body
    • deep_merge merges the configuration in the API request body into the build configuration

これを見ると、「scriptだけ内容を変える」ができそうに思います💡

GitHub ActionsからTravis CIを利用できそう?

次にやるのは、「GitHub ActionsからTravis CIのAPIを叩く」です。
そして、そのリクエスト内容に「script処理をスキップする」といった内容を混ぜたり混ぜなかったりしようと思います。

そう思って探していると、「ActionsからTravis」になんだかお誂え向きのものがありました!

github.com

The Travis CI action wraps the Travis CI API so that new builds can be created based on Push and Pull Request GitHub events.

と書いてあります。これを扱えれば、「ActionsからTravis CIにリクエストを飛ばす」は出来そうな感じがしてきました。
では、今度はtravis-ci/actionsを理解するために、そもそものところである「Actionsから○○を実行する」に関する概念をつかみにいきたいと思います。

GitHub Actionsの正体は、○○を実行させるものだった

travis-ci/actionsを見ると、Dockerfileやentrypointといった単語を発見することができます。ということは、なるほど、なにか「Dockerを使ったもの」であるなと推察できます。
github actions docker でググると、次の記事に行き当たります。

developer.github.com

GitHub Actions are code run in Docker containers.

なるほど、「Dockerコンテナを起動してその中でなんでもやっていいよ!」でしょうか。
そのDockerイメージ/コンテナはどうやって作成されるんだ?

developer.github.com

GitHub Actions execute in Docker containers. An action can use an existing publicly available Docker image, or you can create your own Docker image by including a Dockerfile with your code.

既存のものを使うこともできるし、自作したDockerfileを利用することもできるよ〜と。であれば、恐らく、Actionsの設定のやり方で「どのイメージを使いますか」という設定をする部分があるのでしょう。

ここまできたら、次は「Actionsはどうやって使うんですか」のイメージを掴みに行くのが良さそうです。

Actionsに触ってみる

Actionsへは、GitHubのレポジトリ画面からアクセスできます。

f:id:o0h:20181208161550p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

ここに、Actionsの作成画面があります。

f:id:o0h:20181208161643p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

「Workflow」なる概念が飛び出してきました・・・!

Workflowってなんだ・・?

どうやら大本になる概念な予感がするので、Actionsの最も大元となるdocに書いてあるだろうと期待します。

developer.github.com

サイドメニューを見るに、GitHub Actions = Workflow + Action というイメージですかね?
f:id:o0h:20181207202520p:plain

説明を見てみます。

Workflows are a composite of many different GitHub Actions, or tasks you want to accomplish, and are triggered by webhook events.

Workflowsは、「発動条件」と「発動時の設定」って感じすかね。それで「何をやりますか」というSVOのOに当たる部分が、Actionsである〜という風に理解しました。
それでは、さっきの「どうやったらtravis-ci/actionsを呼び出せるの」は、workflowを作れば何か出てきそうですね。

Workflowを作ってみる!

「Create a new workflow」ボタンを押したら、新規ファイル作成のページに飛びます(ページURLとページタイトルが、new fileのそれ)。
そして、特徴的なGUIが現れます。

f:id:o0h:20181208162034p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

デフォルトでは、コレは同時に octo-test/.github/main.workflow というファイルの新規作成でもあるということが、見て取れます。
隣にあるタブ、 Edit new file ではテキストベースでの編集に入れます。

f:id:o0h:20181208162357p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-file-editor

あ!なんか、このDSLは見覚えがあるぞ。travis-ci/actiosnレポのREADMEに、こんな感じのサンプルがありました。

f:id:o0h:20181207180948p:plain

いろいろと、点と点がつながってきました・・・!

f:id:o0h:20181208162627p:plain
上部の黒い枠が「workflow」で、下の点線で囲まれた部分に「action」を定義していく

Workflowの定義

黒いボックス、 workflowの方にある Edit をクリックすると、次のようなメニューが現れます。

f:id:o0h:20181208163239p:plain

キャプチャ上の Issues になっている部分で、発火条件としたいイベントを選択するという寸法!

どこ見ればいいかな?
定義の一覧があるはずなので、公式docを当たりました。

Workflow configuration options | GitHub Developer Guide

f:id:o0h:20181207181815p:plain

resolve が何者かまだわかっていませんが、とりあえず今の我々に必要なのは on に関する知識でしょう。リンクが貼られているので、GitHub eventについて知識を求めに参ります。

今回のスコープでいうと「tagが作成されたとき」です。Event nameとしては、 create でしょうか。 これに、副条件として「タグ(が作成された)」を加えたいのですが。

具体的な処理を行う前に、イベントをフィルタリングするには?

簡単な比較条件を突っ込んで(branch == 'master' みたいな)弾ければいいのに・・・と思うのですが、どうやらそういう記述はできなさそう。
我々が使える武器は「workflow」と「action」なので、じゃあ「何かプログラマブルに処理を注入するには、actionを重ねるのかな?」と思いました。GUI的にも、actionを多層化してチェインすることはできそうなので。

どうでしょう、もし「簡単なシェルスクリプトを実行する」ためだけのDockerイメージがあれば、これは実現できそうな気がします。
GitHub公式のActionを見てみます。

github.com

bin って、めっちゃそれっぽいな・・・w 開いてみます。

github.com

Usage information for individual commands can be found in their respective directories.

はい。そして、filter というディレクトリがあるので、これにすがってみます。

For example, here is a workflow that publishes a package to npm when the master branch receives a push:

どんぴしゃっぽい!
サンプルを見てみます。

workflow "Build, Test, and Publish" {
  on = "push"
  resolves = ["Publish"]
}

action "Build" {
  uses = "actions/npm@master"
  args = "install"
}

action "Test" {
  needs = "Build"
  uses = "actions/npm@master"
  args = "test"
}

# Filter for master branch
action "Master" {
  needs = "Test"
  uses = "actions/bin/filter@master"
  args = "branch master"
}

action "Publish" {
  needs = "Master"
  uses = "actions/npm@master"
  args = "publish --access public"
  secrets = ["NPM_AUTH_TOKEN"]
}

「masterブランチでフィルタ」をしているのは、 Masterのactionですね。
ついでにというか、さっき疑問だった「どうやって外部のイメージをactionに定義するのか」もこれで解消しました。 use に指定するんですね。そして、依存関係にあるactionは前ステップにあたるあactionをneedsとして宣言する、と。

我々が実行したい createイベントでtagの場合のみ は、filterディレクトリのREADMEに言及があるのでこれをそのまま使います。

.workflowはこのようになります。

workflow "on tag push" {
  on = "create"
  resolves = ["filter-only-tag-created"]
}

action "filter-only-tag-created" {
  uses = "actions/bin/filter@master"
  args = "tag"
}
  • on tag push というworkflowを
    • createイベントに反応して起動させ
    • filter-only-tag-created actionを呼ばせる
  • filter-only-tag-created
    • actions/bin/filter@master" のイメージを利用し
    • tags というパラメータ込みで起動する

actionにおけるuseの指定ですが、

  • 自レポジトリの場合は、./ から初めてPJルートからの相対パス指定
  • 他レポジトリの場合は、 user名/レポジトリ名/ディレクトリへのパス@ブランチ と指定

という形式になるようです。

developer.github.com

filterを設定できたように思うので、Travis CIをキックする処理を入れます。 f:id:o0h:20181207190205p:plain

ここで、request bodyを変えてあげる必要があります。
が、travis-ci/actionsレポでentrypointに指定されているjsの内容を見てみると、どうやらコンテンツが固定されているようです。

自前のDockerfileを用意しよう・・・

どうやら外から注入というのが厳しいような気がしたので、自前で用意してみます。幸い、travis-ci/actionを見ても、複雑な処理は入っていません。なので、丸々っと必要な処理をコピペしてしまいます。

ひとまず、以下のようにしました。

gist.github.com

自レポジトリ内のDockerイメージを利用する方法が、公式ドキュメントに書いてあります。PJルートから、コンテキストとなる = Dockerfileのあるディレクトリへのパスを指定してあげれば良さそうですね。
最終的には、このようになりました。

workflow "on tag push" {
  on = "create"
  resolves = ["deploy"]
}

action "filter-only-tag-created" {
  uses = "actions/bin/filter@master"
  args = "tag"
}

action "deploy" {
  uses = "./.github-aciton/travis-ci"
  needs = ["filter-only-tag-created"]
  secrets = ["TRAVIS_TOKEN"]
}

Travis CIの利用には、TRAVIS_TOKEN が必要です。
https://travis-ci.com/account/preferences で取得し、actionに設定してください。

f:id:o0h:20181208165506p:plain
`Create a new secret` より追加

実行してみる

エディタ右上の、Start commit からコミットします。
その後にタグを打つと、定義したとおりのworkflowが動きます。

その後は、実行結果をグラフィカルに確認が可能です。 GitHub上で「Actionの履歴・実行結果が見れる様子」は、この動画の1:10辺りで垣間見ることができます。

左側に実行されたworkflowの履歴が表示され、その中にあるactionごとの実行結果ステータスが色により表されます。更に、actionのブロック内にあるlogをクリックすると、詳細が確認できるわけです。

まとめ

本来であれば、.travis.ymlもちゃんといじりつつ行うべきだと思っています。
ただ、「新機能のポテンシャル」としては、「ここまで簡単に色々とできる!!」という手応えは間違いなく感じたので、個人的には大満足です。
Dockerでいろいろできる!はやばい。

どんどん活用していくしかねぇな・・・!という温度感です。

参考記事等

*1:もちろん、config等を設けたり環境変数デフラグをもたせて、travis.ymlやそこから呼び出すスクリプトの中でコントロールをする・・という手段はそうていされますが、今回の目的の1つに「あまりtravis.ymlを書き換えたくないな」があるので、こちらは却下

モックライブラリ「Prophecy」と「PHPUnit_Framework_MockObject」の比較

1人AdventのDay-6です。

adventar.org

前回の記事は @o0hさんでした。本日は、私@o0hがお送りします。

さて、皆さんはPHPUnitを利用する際に、モックを使っていますか?
PHPUnitには、標準で2つのモックオブジェクトが入っています。

1つ目が、MockObject です。これがデフォルト・・・という言い方も、「2種類とも最初から使える状態になっている」以上は微妙な感じもするのですが、まぁそんな感じです。
2つ目が、Prophecy です。PHPUnitの公式Docを見ると、以下のように説明がされています。

クセは強いけれども、強力で柔軟な、PHP のオブジェクトモッキングフレームワークです。 最初は phpspec2 のニーズを満たすために作られましたが、今やそれ以外のテスティングフレームワークでも、 最小限の努力で使えるようになりました」

・・・よくわかんないですね!
ということで、今回は「この2つを実際に使ってみるとどんな感じになるの」を比べて見たいと思います。

本題に入る前に

Prophecyについて

まず、Prophecyについてはこちらの記事が大変参考になるので、ご一読されることをおすすめします。

qiita.com

ダブル、スタブ、モック、スパイ

モックオブジェクトについて語るので、よく混同される用語の整理をまずしておくのが良いかと思います。 ・・・と思ったのですが、シンプルに纏めてくれてい記事があったのでご紹介を。

qiita.com

といっておいてなんですが、本記事中では特に意識せずに「モック」という言葉を利用します。モック方式・スパイ方式という言い方をした場合にだけ、テスト作法に関しての話題にフォーカスしているものとして、使い分けているとご理解いただければと。

モックっていつ使うの?

「テストが難しいもの」を登場させる必要があった場合に利用しますね。よく「インターフェイスに対してテストをかけ」などといいますが。
例えば、「決済APIを叩く処理」についての実装をしているとき、そのテストは「テストを実行するたびに決済を行う」わけには行きません。そこで、決済という外部システム=「依存コンポーネント」をモック化してしまえ、見たいな話です。

「決済して、その結果を返す部分」について「決済に必要なパラメータを受け取って、実行したふりをして値を返す」という振る舞いをする"ハリボテ"。それが、モックです。

PHPUnitでモックを使ってみよう

こんなに雑なサンプルコードを用意してみました!「あ、外部サービスを叩いているのねふむふむ」くらいのイメージをしてください。それ以上でも以下でもありません。

<?php
namespace App;

use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;

/**
 * Class HogeService
 * @package App
 */
class HogeService
{
    /**
     * HogeService constructor
     */
    public function __construct()
    {
        $this->http = new Client();
    }

    /**
     * Request user object to Hoge
     * 
     * @param int $id user id to request
     * @return ResponseInterface
     */
    public function getUser(int $id)
    {
        return $this->http->get("/user/{$id}");
    }
}

さて、 getUser()の単体テストを書きたいわけですが、ここで「テストの実行のたびに実際に「ほげ」にアクセスさせるわけには行きません。*1

また、HogeServiceはGuzzleHttp\Clientに依存しています。ということで、「実際にテストが困難な依存コンポーネント」として、Clientをモック化してしまえ!!という捉え方をしました。

実際にモックを用いたテストコード

ここからは、「モックを使ってみよう」のコーナーです。

まず簡単に、次のようなテストクラスを作成しておきます。

<?php
namespace App;

use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;

/**
 * Class HogeService
 * @package App
 */
class HogeService
{
    /**
     * HogeService constructor
     */
    public function __construct($client = null)
    {
        $this->http = $client ?? new Client();
    }

    /**
     * Request user object to Hoge
     *
     * @param int $id user id to requestc
     * @return ResponseInterface
     */
    public function getUser(int $id)
    {
        return $this->http->get("/user/{$id}");
    }
}

ここに、説明用にいろいろとテストケースを追加していきます。

1. PHPUnit MockObjectのpartial mockを用いた例

まずは、以下の内容で検査をできると考えました。

  • HogeService::getUser($userId)の呼び出し実行時に
  • Client::get()に対して
  • /user/:user_id を引数として
  • 呼び出しが1回行われている

これをもって、「ちゃんとHogeをキックできている」と判断することとします

素直に書いてしまえば、この様になりました。

<?php

    /**
     * test to get()
     */
    public function testGet()
    {
        $userId = 100;

        /** @var MockObject|HogeService $mock */
        $httpMock = $this->createPartialMock(Client::class, ['get']);
        $httpMock->expects($this->once())
            ->method('get')
            ->with("/user/{$userId}");
        $this->subject->http = $httpMock;


        $this->subject->getUser($userId);
    }
  1. createPartialMock() で、partial mockを作ります
    • これは、「指定したメソッドだけ無効化=モック化する」という方法です
  2. 「1回だけ」「getメソッドが」「user取得用パス文字列を引数として」呼び出されることを期待する・・・というモックを作成する
  3. そのモックを、subjetクラスのメンバとして注入し
  4. 実際に、getUser()をコールしてみる!

という内容です。これで、もし「期待されたとおりの内容で呼び出されていない」となると、テストケースにfailureが記録されます。

2. Prophecyのモック方式を用いた例

さて、同じ発想で今度はProphecyを利用して書いてみます。

<?php
    /**
     * test to get()
     */
    public function testGetByProphecy()
    {
        $userId = 100;

        /** @var ObjectProphecy|Client $clientP */
        $clientP = $this->prophesize(Client::class);
        $clientP->get("/user/{$userId}")->shouldBeCalledOnce();
        $this->subject->http = $clientP->reveal();


        $this->subject->getUser($userId);
    }

事前に「何が、何回、どうやって呼ばれるか」を宣言しておく持っk方式です。

  1. prophesizeに対象クラスを渡して、ObjectProphecyインスタンスを取得します
  2. get()が実際に呼び出されるような形式でmethodを書き・・・ 「1度だけ呼び出されるべき」という指定を最後にくっつけます
  3. reveal()により、モック*2を取得します

あとは実際に、subject->getUser()をコールしてみて、どうなるかな?といった具合です。

3. Prophecyのスパイ方式を用いた場合

今度は、事後に「呼ばれたかな?」を検査するスパイ方式です。

<?php
    /**
     * test to get()
     */
    public function testGetByProphecySpy()
    {
        $userId = 100;

        /** @var ObjectProphecy|Client $clientP */
        $clientP = $this->prophesize(Client::class);
        $this->subject->http = $clientP->reveal();

        $this->subject->getUser($userId);

        $clientP->get("/user/{$userId}")->shouldHaveBeenCalledOnce();
    }
  1. いきなりrevealして
  2. subject側の実行が終わってから、 「have been called: shouldHaveBeenCalled()」と検査する

という違いです。

さて、これで「MockObject」「Prophecyのモック方式」「スパイ方式」の3種類の書き方を抑えました。

Prophecyは「モックしたクラス」として型の制約をパスできる

例えば、HogeService::setClient() という利用するクライアントオブジェクトを注入できるメソッドを用意したとします。

<?php
    /**
     * HogeService constructor
     *
     * @param ?Client Client to use..
     */
    public function __construct(?$client = null)
    {
        $this->setClient($client ?? new Client());
    }

    /**
     * Inject Client object.
     *
     * @param Client $client
     */
    public function setClient(Client $client)
    {
        $this->http = $client;
    }

これに関連して、先程用意したテストケースを書き換えます

<?php
     /**
     * test to get()
     */
    public function testGetByProphecySpy()
    {
        $userId = 100;

        /** @var ObjectProphecy|Client $clientP */
        $clientP = $this->prophesize(Client::class);
        $this->subject->setClient($clientP->reveal());

        $this->subject->getUser($userId);

        $clientP->get("/user/{$userId}")->shouldHaveBeenCalledOnce();
    }

reveal()で得たオブジェクトは、これでも受け入れられることができます。

感想

Prophecyは、思想として「絶対に部分的モックなんてやるものか!」というのがある、と先程のHirakuさんの記事で紹介sれていました。テストの美しさ、そもそもあるべき思想として「モックが実際の動作を部分的に実現している状態」への気持ち悪さ・不完全さというのは、何となく分かる部分もあります。
それゆえに、PHPUnitもMockObjectは「部分的モックが使える」という点は、ある意味では強みでもあるのかな〜と感じました。実際、「挙動をぶっ壊してモックとする部分は最小限に抑えたほうが安心」という場合も、ないとも言い切れないとも思うからです。

対して、記述の簡潔さはProphecyの方が優位だな・・?と感じています。どう考えても、標準のMockObjectのほうが「癖が強い」のでは・・・・・・? ;;
expectsの指定もそうですし、スパイ方式での検査の提供によって、実際に検査する箇所を物理的に「actualの実行のあと」に持ってこれる、というのはテストコードの「見た目の気持ちよさ」において魅力を感じるのです。
最もベーシックな単体テストは、「実行結果をメモしてそれについてassertionを行う」というものでしょう。つまり、実行→結果検査という順序で記述がなされます。それを、「事前設定式」のモック方式の場合、期待する内容 -> 実行となっているわけです。冷静に考えると、これは気持ち悪いかもしれない・・・

そんなところで、隙があらばProphecyの実戦投入も狙ってみたいな!と思った次第でした😁

*1:なお、このコードはそもそもアクセス可能なURIを生成できないので、外部アクセスを生じないのですが

*2:スパイ方式で利用することもあるので、doubleという方が正しい

SymfonyはPHP-FIGから離脱するのかな?

1人AdventのDay-5です。

adventar.org

PHP的にはそろそろPHP7.3が出るよ〜ってことで、明るい話をしたいなぁ!って頃だと思うのですが。。今日は「最近あったニュース」でいう事では、個人的にこちらも注目しています。

※ 本記事は、私が普段Symfonyやそのコミュニティに接していなかったり、PHP-FIGの動向・思想やメンバーの変遷についても過去から深く追っていた訳ではないので、間違いに気づいたりご指摘をいただけたら積極的に訂正していきたいスタイルです・・・

なお、書くにあたって情報を収集していたところ、とても良くまとまっている記事が見つかりました。
こちらもぜひご覧ください。

hub.packtpub.com

何が起きているのか? / 事の発端

Remove Symfony by fabpot · Pull Request #1120 · php-fig/fig-standards · GitHub

From 6fea4246955bf33648579f6822146dd49b48383e Mon Sep 17 00:00:00 2001
From: Fabien Potencier <fabien@potencier.org>
Date: Tue, 20 Nov 2018 19:19:51 +0100
Subject: [PATCH] Remove Symfony

---
 personnel.md | 2 --
 1 file changed, 2 deletions(-)

diff --git a/personnel.md b/personnel.md
index a7a48d684..519bd1360 100644
--- a/personnel.md
+++ b/personnel.md
@@ -67,7 +67,6 @@ Feel free to contact the secretaries at info AT php-fig.org. For more informatio
 | [Stash]                           | Robert Hafner ([@tedivm])             |
 | [Stormpath PHP SDK]               | Brian Retterer ([@bretterer])         |
 | [SugarCRM]                        | Andreas Sandberg ([@yellowandy])      |
-| [Symfony]                         | Fabien Potencier ([@fabpot])          |
 | [Flow] and [Neos]                 | Karsten Dambekalns ([@kdambekalns])   |
 | [Wikibase] and [Semantic Media]   | Jeroen De Dauw ([@JeroenDeDauw])      |
 | [Yii framework]                   | Alexander Makarov ([@sam_dark])       |
@@ -214,7 +213,6 @@ Feel free to contact the secretaries at info AT php-fig.org. For more informatio
 [Stash]: http://www.tedivm.com/stash
 [Stormpath PHP SDK]: http://www.stormpath.com
 [SugarCRM]: http://developers.sugarcrm.com/wordpress
-[Symfony]: http://www.symfony.com
 [The League of Extraordinary Packages]: http://thephpleague.com
 [Wikibase]: http://www.wikiba.se
 [Yii framework]: http://www.yiiframework.com

ということで、中々にインパクトのあるdiffだな!という内容のPRが、提出されました。

本題に入る前に

蛇足っぽいのですが、この話のスコープをはっきりさせるためにいま一度整理します。

PHP-FIGについて

PHP-FIGは、 PHP Framework Interop Group という集団です。
もともとは、「PHPに標準を送ろうぜ!」みたいな動機で集まってきました(雑)。
発足とそのあとの歴史については、この記事にまとまっているので御覧ください。

www.sitepoint.com

諸々を端折って、今は、「FIG」を名乗っているわけです。その目的は、(過去の反省を踏まえて「より柔軟・踏み込んだ提案をしていく」というスタンスに基づき)「フレームワーク間の相互運用を推し進める」ことにあります。

例えばPSR-0 autoloading standard *1などは、「PHP5.3で名前空間が入って、便利だけど、命名規則とかばらけちゃうとね」「もっとポテンシャル生かしてちゃんと使いたいよね」といった問題意識に端を発している訳です。
今では後継のPSR-4がPHPの「スタンダード」ですが、composerを含む「多種多様なライブラリを気軽に使える」エコシステムは、こうした提案力を持った存在により成立・推進されている面は大いにあると思います。ライブラリの作者からしても、より汎用性の高いものを作る上で、明確に「よくある」といえる実装やパターンがあると助かることもあるのではないでしょうか。そして、ライブラリの作成に関わる人数が増え、手が増えていくことで質の高いソフトウェアも生まれ・・・というサイクルがあると思います。

PSRについて

PSRは、有力なフレームワークの代表者や個人といった有識者が採択などを行っています*2。 その内容には、コーディングスタイルのガイドからキャッシュの使い方、DIコンテナに関する議論まで多岐にわたります。
これを「利用者が増えてくれば、その利用者の受ける恩恵は増えていく」というのを理想としてはいると思いますが、必ずしも「参画したら従うこと」と取り決めているわけではありません。
PHP-FIGホームページに有るFAQを見ると、以下のように記述がされております。

[Q] Do voting members have to comply to the standards?
[A] No. Becoming a voting member on the PHP-FIG in no way forces a member or project to implement every - or any - accepted PSRs. Projects have to consider backwards-compatibility issues when upgrading and make the changes at the right time, so it is assumed most projects will eventually adopt, but it is not a requirement.

Frequently Asked Questions - PHP-FIG

「強制的に従わなくちゃいけないものでもないし、互換性とかあると思うし、使うものを好きに選んでね」という話になっています。

PHP-FIGは、様々なプロジェクトが「相互運用性を尊重」した作成するための共通基盤として、素晴らしいやり方「だった」

さて、件のツイートに立ち返ってみます。

Fabien Potencier氏(@fabpot)はSymfonyのオリジナルの作者で、SynfonyはPHP-FIGのメンバープロジェクトです。「PHPの世界でのメジャーが集まって方向性を決めよう」というPHP-FIGの性質上、歴史・知名度・普及度のいずれの観点から見てもSymfonyは重要なプレイヤーといえるでしょう。

そんな @fabpot 氏が、過去形で「相互運用性のため」に「よいもの だった 」と語っているわけです。

スレッドを見ると、何が「よく」て何が「悪かっ」たのかが語られています。

めちゃくちゃな超訳もどきにはなりますが、大体こんな事を言っています

  • PSR-0/4: autoloadingは、ライブラリを互いに乗り入れるための最高の1歩だった
  • PSR-1/2: conding-standardは、些末な事に気を取られずに上手くやるのを推し進めてくれた
  • PSR-11: containerは協働のための見本。私を含め、最初こそ批判的に見ていた人はいるが、既存PJをより簡単にDIに乗っけていくための落とし所を見つけることができた。

このあたりは、"相互"のためにという思想を体現しているな〜と賛成しているように見えます。
PSR-7/14に関する言及は、かなり批判的です。

www.php-fig.org

github.com

  • PSR-7は、相互運用のためのものではない。結局の所、なにか(FIGが)「新しいフレームワークを作り始めた」ようなもの
  • PSR-14も同じ道を辿るだろうな

なお、まだ私は具体的な言及を探してはいないのですが、PSR-7に対して「思想に反する」「やりすぎ」のような立場をとっているのですから、これを前提とした15/17/18についても「そうじゃない!」という感じなのかなーと思います。。

そして、「PHP-FIGは、もう(多様なプロジェクトの)相互運用のためのものでもないし、彼らが独自的なカラーを持ったフレームワークを作るためにあるんだ」とまで言います。

「相互運用可能な」とは

PHPは、言語自体の思想としても、多くのフレームワークにしても「互換性を保つ」ことを非常に大事にしているように感じます。また、先程述べたとおり、PHP-FIGも「後方互換性を尊重し、PSRを実装しない事は大いに有り得る(可能である)」としているわけです。

そのうちにあって、PSR-7(やPSR-14)が「終わりの始まり」とされているわけですが・・・
同じくSymfony勢から、Titouan Galopin氏のこのリプライが問題をわかりやすく描き出します。

「既存のやり方を、寄せていく道がない。そんなのは、「相互運用」とは言えないでしょう。」という失望を感じます。

PHP-FIGはどうなっちゃうの・・?

PR自体はまだマージされていないのですが、これ離脱しないシナリオあるのかな・・・・
FIGは、過去にLaravelやGuzzle、Auraといったプロジェクトが離脱しています。そこに今回のSymfonyの離脱となると、「いろんなフレームワークとかでよく見かける名前」が大きく減りそうだなぁ、と個人的には感じるところ。これらのプレイヤーたちが「PSRに物申す」立場でなくなっていくというのは「策定されたPSRを利用する」可能性も落ちるわけで、「PSRってあまり見かけないよね」な状態になってしまったら・・存在意義が問われそうです。

また、実装(API)が揃っていても、現実的には、結局の所「どこへでも、持ってきてすぐに使える」という状況は作れていないし今後もそうなのだと思います。それも踏まえ、「必要とされる共通のインターフェイスとは」については、今までと少し考え方が変わっていくのでしょうか・・・・?

「やりやすくするため」の仕組みづくりだと思うので、「やりにくいから抜ける」というのは悲しみがありますね・・

*1:これがまさに、「PHP Standards Group」の当初の目的の1つ

*2:プロセスにはこちらを参照 https://www.php-fig.org/bylaws/psr-workflow/