uzullaがブログ

uzullaがブログです。

PHP Conference fukuoka 2019 に参加してきました!

おはようございます、uzullaです。

ついに5年目となるPHPConference fukuoka 2019に参加してきました。

(福岡は天気はよろしくなかったんですが、雨はそこまでふらなくてよかった)

まとめ

相変わらずのPHPconfukだったので、来年も参加したいですね。

袋詰サポーター

カンファレンスをやる身としては大変さをしっているので、袋詰め作業をお手伝いしました。

いつもの数倍の速度で終わったとのことでめでたかったですね。

こういうカンファレンスのサポートの仕方もあると思います!!

(これをみると、PHPConfukにきたという気分がするw)

非公式前夜祭

ここ数年スピーカーではないので、非公式前夜祭参加です*1

前回よりも近い距離のイベントでした。

これは例年儀式の様子です

しかし、Udzura(福岡のうづら)さんのトークがよかったですね!!

PHP関係ないと本人はいってましたが、これはぜひPHPに持ち込んでいきたいと思ったり。

bashで実行しているコマンドのSniffとか、すごいわかりやすい例が最初にきたのがよかったです!

相変わらずLINE 福岡さんのカフェはきれいだ。

そして福岡の夜景はきれい。

第一エールズ

その後は一旦ホテルにかえってうだうだした後、エールズにいきました。

最近色々な諸事情であんまりお酒はのまなかったんですけど(いやまあ、ビールとハイボール1〜2杯づつくらいはのみましたがw)、翌日にむけてもりあがっていきました。

福岡の人とお話するのはなかなかすくないんで、最近の近況報告したりなんだりでよかったですね。

当日

酒を抑えたからか、朝6時にきっちり起床、というか早すぎ。吉野家にいって飯を食って会場近くのベローチェで時間を潰す。

入場&オープニング

うっかり午前六時に起きてしまったので、毎年もらいそびれているTシャツをゲット(XXLの在庫数が少ない的な意味で)早起きも得になりますね。

(名札シールにPerlがない!!!!w)

相変わらずPHPconfukのOPは丁寧です、やっぱ東京の人間なので、スポンサーの会社さんをしらないんですよね、どういう会社かしれてよかった。

トーク

ということで、さまざまなトークを聞きます。

コーヒーのあるスポンサー部屋にいったり、参加者と交流したりなんだりして午前中は過ごしました。

(コーヒー部屋でみた、長谷川さんの自作CPU)

昼食

昼飯はラーメンです。

FFBの前にあるラーメン屋、いつも混んでるな!っておもってたんだけど、ついにいけました。

美味しかったです!

結構ならびましたけど、知り合いと並んでるとならんでいる時間も苦にはならないですね。

マネクラからの挑戦状

午後のトークを一本だけきいた後、

午後は気になっていたマネクラからの挑戦状に挑戦です。

私はマネクラには縁があり、アルファテスターから使っています。まあ最近はあんまりつかえてないんですが…。

さておき、挑戦状はCTFとコードゴルフがあり、豪華。この2つならCTFのほうが自分にはあってるかなと挑戦。

全5問で、4問はPHP+Webが詳しいなら解ける感じでした。かなり難易度は調整されていて、カンファレンスの一幕としてはとても良かったと思います!

最後の一問(といいつつ、途中「これが一番むずかしいですよ」っていわれて、小癪な!と順番無視してやってましたが)は本当にCTFっぽい問題でよかったです。ただ、解き方が合ってたのかはよくわからないw

私は大人気なかったので、めちゃめちゃマジで集中して、一位で全クリです!!!

10万円のクーポンをゲットしましたがまだ使いみちを思いついていませんw健全かつ三方良しないい使いみちがあればぜひ教えてください!!!

一旦部屋を離れて午後のトークを聴いてたんですが、どうせだからゴルフもやるかァ!と思って部屋にもどってきたら、めもりーさんが無双していてやばい。

スコアがのびるたびに桁が下がってくのはかっこいいですねw

私はロジックコードを触りたくなくて、無用なファイル削除、vendor以下の刈込み、vender以下のMinify(w)vender以下のファイルをgzip(笑)して、オートローダーを修正してStream wrapperでよみこむようにして、テンプレートもgzipでオートロード。

というかんじで20万点くらいでした。

ただ、めもりーさんが7万点とかだしたので、これはコード触らないやり方では勝てねえな!!ってかんじで諦めましたw

(これは翌日ですけど、すごい)

後で振り返るに、vendorを消すというのが当然っぽかったので、vendor以下しか触らないみたいなやり方ではちょっと厳しい感じでしたね、まあ楽しかったんですけど!

(翌日は気合い入れてチート全開、おじさんの往生際のわるさを発揮してしまったのですが、それはまた別の話)

そうこうやっていたら、私がちょっと行動チョンボしてLTとかクロージングは見れなかったので省略です。

あとはまあ、懇親会、

第二エールズ、

元祖長浜とこなして福岡の夜はふけていきました…。

Fusicさま、アフターハック

本編の翌日、ここ数年すごくたのしみにしているイベントです!

わたしが前日にクリアした挑戦の解説トークをしたり、ゴルフについて数名のおじさんが正攻法したり、インチキをいい笑顔で話し合って実際に実装して笑いながら解説したりしていました。

いろいろチートをした私のホールアウトの様子です。

りろんじょうこれは一位でしょ!(0点以下、マイナスとかバグでしょw)となったので、満足ですw

他にトライしている人にも解説もしたんですが、ISUCONで学んだ通り(??)現状把握と俯瞰、計測が一番重要で、いきなりコードを触らないのが勝利のポイントですよね。

ということで、アフターハックは自由なライブ感のあるトークあり、教え合ったりあり、酒あり(私は飲まなかったですけど!)ご飯あり、のイベントで最高ですね。フュージックさん本当にありがとうございます!!

https://fusic.co.jp/ (メインホールの、FusicホールのFusicさんです)

私は私用もあり、16時くらいに離脱し、九州新幹線に乗り込んだのでした…!

まとめ

PHPConfukたのしかったです。

まあ最近(キワモノばかりをSubmitするからだろうが)トーク通せてないのですが、若い人、新しい人もトークしていてそこのあたり良いと思います。

地域性の高いカンファレンスなので、東京のでかいカンファレンスとは違うところもあって、そこの色の違いが好きでもあります。

ありがとうございました!!!

また来年よろしくお願いいたします!

*1:公式前夜祭は関係者のみ

GoのバイナリをPHPスクリプトとしても扱う

codehex.hateblo.jp

$ ./main
This is Go world!!
今度は Perl で実行してみましょう

$ perl -x ./main
Hello, Perl World!!
ワオ!!

ほほう。

phpではどうすべきか

-xオプションはないが、<?phpまではSTDOUTにそのまま出力されてコードとしては解釈されません。なので…

$ cat main.go
package main

import (
    "fmt"
    "io/ioutil"
)

const script = `<?php
file_put_contents("php://stderr", "This is PHP world!!!".PHP_EOL);
__halt_compiler();
`

func init() {
    ioutil.Discard.Write([]byte(script))
}

func main() {
    fmt.Println("This is Go world!!")
}

$ go build -o main main.go

$ ./main
This is Go world!!

$ php ./main 2>&1 > /dev/null
This is PHP world!!!

こちらからは以上です。

なお、このエントリは8分でかかれました。

(が、やり方をちゃんと理解してなかったので、なおしました…)

f:id:uzulla:20190702191852p:plain

header後にdieするテストのアンチパターン

明後日土曜日はPHPConference Fukuoka 2019ですね、楽しみなuzullaです。

さて、Hachioji.pmのSlackでPHPのテスト手法について質問があって、結果的にその解決策はいいのが無かったんですけど、過去こういうことはしたことがあるよってメモ書きです。

エントリ名に「header後にdieするテストのアンチパターン」とかきましたが、そもそもheader() からのexit() コンボがアンチパターンですので、イケてるフレームワークを正しく使う方々には本エントリは役立たないはずです。

PHPのexitは終了してしまう

さて、phpで以下みたいな「リダイレクトした直後にexit」というコードはしばしば見かけるものです。

<?php
if($need_redirect){
  header("Location: /");
  exit;
}

ぐぐってみるとわかりますが、本当に多くて、本当かよと思います。しかし、実際俺もかいてた事があるので本当です。

で、こういうコードをテストしようと思うと、理論上以下みたいになるので、phpunitが終了します。

<?php
use PHPUnit\Framework\TestCase;

class MykTest extends TestCase
{
    public function testOne()
    {
      //実際にはここにべた書きするわけないですが
        if($need_redirect){
          header("Location: /");
          exit;
        }
    }

    public function testTwo()
    {
        // ここがテストできない
    }
}

つらい。

こういうこともできない

register_shutdown_functionとかを知っていると、以下みたいにかけるかなと思いますが、わりとできません。

<?php
use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    /**
     * @test
     * @expectedException \Exception
     */
    public function testOne()
    {
        register_shutdown_function(function () {
            throw new \Exception("don't die!");
        });

        exit();
    }

    public function testTwo()
    {
        echo "plz test me!!!";
    }
}

register_shutdown_functionのスコープはphpunitの外にとびでちゃうので、ここで投げてもphpunitがキャッチしてくれません(というふうに私は認識しています)

そもそもexitはphpunitをまるごと殺そうとしますので、キャッチできたとしてもdieまっしぐらです。

evalもだめ

<?php
use PHPUnit\Framework\TestCase;

class MyTest3 extends TestCase
{
    public function testOne()
    {
        eval("exit();");
    }

    public function testTwo()
    {
        echo "plz test me!!!";
    }
}

他の言語の経験があると、一見できそうです。でもevalはやっぱりこのスコープで実行されるのでダメです。(終了しちゃいます)

https://www.php.net/manual/ja/function.eval.php

解決策は?

無い(迫真)

無いので、以下みたいなものもあるようです。ブクマも数件ついてます、皆さん悩みは一緒ですね。

github.com

緩募:Pure PHPで、exit/dieでphpunitを殺さない黒魔術をご存知の方は教えてください。

さて、「runkitは?」という声もきこえてきますが、exitは関数ではない。

(動的な)ソースフィルタは?という怖い声がきこえてきますが、尋常な精神では無理。

「いやいや、コードをリファクタしようよ!」という正気の意見はここでは黙殺します、世の中にはできないこともある。

「exitを例外に単純置換しよう!」といったある意味PHPerらしい動けばいい系アドホック解決策は、解決はするかもしれませんが早晩似たような地獄に到達するのでやめたほうがよいです。

(え?exitの代わりにユーザー定義な致命的Errorを投げる?…フム、興味深さもありますね)

で、結局、exitが絡む時にphpunitを殺さない為には別プロセスで実行するしかない*1。みんな大好きなexecしましょう(正気かギリギリのライン)

事案

で、「私の場合は」あるリダイレクトがちゃんと指定のURLにリダイレクトされているか確認したかったことがある。

非常にアレなのでわすれてましたが、こういうことを過去してしまいました。

<?php
use PHPUnit\Framework\TestCase;

class MyTest3 extends TestCase
{
    public function testOne()
    {
        // dumb_exit.phpは勿論最初からファイルをつくっておいておいてもよい
        file_put_contents("dumb_exit.php", "<?php 
        register_shutdown_function(function(){
            echo var_export(headers_list()); // ウッ
        });

        // アプリをうごかしているような体裁を泣きながら整える
        $_SERVER['REQUEST_URI'] = "/hogehoge";
        $_GET['hoge'] = "fuge";
        require_once(__DIR__.'/vendor/autoload.php');

        WebApp::bootup();
        // この中で、
        // header(\"Location: http://example.jp/\");
        // exit();
        // が実行されるとおもってください
        ");
      
        exec("php-cgi dumb_exit.php", $out, $exitCode); // php-cgiでないとヘッダーがとれない

        $this->assertEquals(0, $exitCode); // お好みで

        $stdout = implode("\n", $out);
        list($header, $body) = preg_split("/\n\n/u", $stdout, 2); // ヘッダー分離

        $header_list = eval("return {$body} ;"); // アッ
        $this->assertEquals(1, count(preg_grep("|\Alocation: https://example.jp/\z|ui", $header_list)));
    }

    public function testTwo()
    {
        $this->assertIsString("plz test me!!!");
    }
}

安易に真似されても何なので説明は省略しますが、exitが呼ばれた時にセットされていたヘッダーを、標準出力の文字列経由で配列で受け取って検証する、という感じです。

これはregister_shutdown_functionの中でも、headers_list()はうごくし、var_exportと標準出力をうまいことすればとれないことはない(気もする)という話でもあるのですが、

書いておいて唐突ですが、これは本当によくないので、アンチパターンですね、マネしないようにしましょう!!

(なお、「headers_listしなくても出力のヘッダーを分離すればいいだけじゃん?」というご質問については全くそうなのですが、私は文字列操作が嫌いだったようです、上の中でもめっちゃしてるくせに)

がんばってseleniumとかpuppeteerとかでE2Eしような!!

header吐いてexitしちゃう、しかもreq/resオブジェクトみたいなは無いし、テストできない。そういったレガい案件ではすなおにE2Eするのがセオリーなのだと思います。俺だって人にきかれたらそう答える。

しかし、人も金もなく、ガンガン走らせたいユニットテストごときにヘッドレスブラウザを飼育するのもコスパがよいとはいえません。そのために人はこういった苦難の道を歩みがち、実に人生という感じがします。

素直に最初テスタブルなアプリにしましょう。

しかし人生には取り返しのつかない事だってあるよね。

こちらからは以上です。

*1:そういえばforkで解決…できるのかな…?