2019-06-12

streamWrapperを使ってPHPの任意の関数をモックする

PHPではstreamWrapperを使って特定のプロトコルに対するファイル入出力をフックすることができます。

これを使ってcurl関数をモックしたものがphp-vcrで技術的な詳細は以下の記事に書いてあります。

任意の関数をモックしてみる

今回はこのstreamWrapperを使ってmail関数をモックしてみようと思います。

streamWrapperは特定の関数を持つクラスになります。インターフェースをimplementsとかではなくダックタイプ的な感じで関数を定義していきます。

<?php

class MailMockStreamWrapper
{
    private $content;
    private $position;
    
    private function register()
    {
        stream_wrapper_unregister("file");
        stream_wrapper_register("file", __CLASS__);
    }

    public function stream_open($path)
    {
        stream_wrapper_restore("file");
        $content = file_get_contents($path);
        $this->content = str_replace("mail", "MailMock::mail", $content);
        $this->register();
        return true;
    }

    public function stream_read($count)
    {
        $ret = substr($this->content, $this->position, $count);
        $this->position += strlen($ret);
        return $ret;
    }

    public function stream_stat()
    {
        return [];
    }

    public function stream_eof()
    {
        return $this->position >= strlen($this->content);
    }
}

stream_openでファイルをすべて読みこんでstream_readで文字列の変数から中身を取り出す、という感じにしています。 (一般的にはopenではファイルをオープンするだけでreadのタイミングで読み込むのが良いと思います)

ファイルを読み込む直前でstream_wrapper_restoreしておかないとfile_get_contentsで再度stream_openが呼ばれ、無限再帰するため最初にrestoreで止めておく必要があります。

あとはモックするクラス・メソッドを適当に定義してstream_wrapper_unregister, registerします。

class MailMock
{
    public static function mail($to, $subject)
    {
        echo "to => $to, subject => $subject";
    }
}

stream_wrapper_unregister("file");
stream_wrapper_register("file", "MailMockStreamWrapper");

この状態で適当なコードをrequireすると、requireしたファイルのmail関数がMailMock::mailに置換されて実行されます。

ただ、このコードだと file_get_contents などの単純にファイルの中身を読み込む処理に関してもモックされてしまうので、実践投入するにはファイルパス等のチェックを入れる必要があります。

このエントリーをはてなブックマークに追加