依存性注入とLaravelのIoCクラス(応用)

タグ: Laravel3   依存性注入  

応用編では依存性の注入より、IoCクラスの説明が中心となります。実際、基本編で説明した内容でしたら、IoCクラスと同等のクラスは簡単に自前で用意できそうだと思っている方も多いことでしょう。実際には、IoCクラスには、もう少し機能があります。

まずは、実際にIoCをテストにどう役立てるかご覧ください。

実際の使用法

実用的にはIoCコンテナの定義をまとめたPHPファイルを作成し、それを読み込みましょう。例えば、application/DI.phpを作成し次のように定義していきます。

if (! IoC::registered('apple') ) {
    IoC::register('apple', function() {
        return new Apple;
    });
}

if (! IoC::registered('orange') ) {
    IoC::register('orange', function() {
        return new Orange;
    });
}

if (! IoC::registered('banana') ) {
    IoC::register('banana', function() {
        return new Banana;
    });
}

このファイル中には実際に使用するクラスを定義していきます。このファイルを例えばroutes.phpやstart.phpでインクルードしましょう。

では、少し実例で考えて行きましょう。単体テストを行いたいAppleクラスが存在しています。

class Apple {
    function get_name() {
        return 'Apple';
    }
    function get_mix_name() {
       $fruit = IoC::('orange');
       return get_name.$fruit->get_name();
    }
}

登録名orangeで生成されるクラスはOrangeです。

class Orange {
    function get_name() {
        return 'Orange';
    }
}

Appleクラスのget_mix_name()を呼びだせば、'AppleOrange'が返ってきます。実用的ではありませんが、シンプルな例で考えましょう。

さて、テストです。AppleクラスとOrangeクラスは直接結びついていません。ですから、Orangeクラスを色々と入れ替え、テストが実行できます。

テストのために特定のクラスを入れ替える場合は、テスト用の定義ファイルをrequireしましょう。例えばまずテスト用のOrangeの代替クラス、(テスト)スタブを用意しましょう。application/models/test/apple/stub/orange1.phpに以下のファイルを作成してください。

class Test_Apple_Stub_Orange1 {
    function get_name() {
        return 'OrangeStub';
    }
}

このスタブをIoCに登録するPHPファイルを作成します。例えばapplication/tests/application/models/apple_test_stub_orange1.phpとしましょう。

IoC::register('Orange', function() {
    return new Test_Apple_Stub_Orange1;
}

このスタブの定義はif文により、定義済みであるかどうかをチェックしません。IoCクラスは同じ登録名が登録された場合、後で登録した方を有効とします。本番用とスタブ用のファイルの読み込み順序に関わらず、スタブ用が優先的に適用されるように、本番用の定義ではif文で再定義を避けてあります。

ですから安心して、PHPUnitのテストクラスに、スタブ用のIoC登録PHPファイルを読み込みましょう。

<?php

// orange登録名をスタブへ切り替え
include_once path('app').'tests/application/models/apple_test_stub_orange1.php';

class AppleTest extends PHPUnit_Framework_TestCase
{
    protected $object;

    protected function setUp()
    {
         // appleのテストのため、この場合は素直にnewしても良い。
         $this->object = Ioc::resolve('apple');
    }

    public function testGetName()
    {
        $this->assertEquals('AppleOrangeStub', $this->object->get_name(), 'Orange1スタブを使用したAppleクラス、get_name()テスト');
    }

}

この例では一つだけ紹介しました。IoCクラスは同名であればあとで登録した内容が有効になりました。ですから、テストスタブをどんどん切り替え、先のIoC登録を上書きすることで、様々なテストが続けて行えますね。

シングルトン

IoCクラスはIoCコンテナという呼ばれ方もします。このコンテナという言葉は、デザインパターンの中で、生成したクラスを管理する役割のクラスに使用されます。

そのデザインパターンの一つに、インスタンスが一つであることを保証してくれる仕組みがあります。もちろん、パターン通りにクラスで実現しても良いのですが、やや大げさになります。

LaravelのIoCクラスは、登録に使用するメソッドを切り換えるだけでシングルトンが実現できます。何かを制御する役割を持つクラスは通常一つだけ存在していないと上手く動作しません。大きく資源を使用するクラスのインスタンスがいくつも生成されるのは、通常好ましくないでしょう。そういうインスタンスを生成する場合に使用できます。

もしくは、インスタンスを様々な場所で使うためのお手軽な方法としても使用できます。通常、ソースコードのどこでも使用できるようにするには、グローバル変数に入れるというやや反則気味な方法とか、逆に真面目に引数で渡してやるとかする必要があります。そんな場合IoCコンテナのシングルトンとすることで、ひとつのインスタンスをアチラコチラで使用できます。

登録メソッドをregister()からsingleton()に変更するだけです。

IoC::singleton('big-class' function () {
    return new BigClass;
});

何度big-classをIoCクラスでresolve()しても、この生成コードは最初の一回しか実行されません。二回目以降は、最初に生成したインスタンスを返却します。

コントローラー

Laravelでは、コントローラークラスもIoCに登録できます。

IoC::register('controller: post-controller', function() {
    return new Post_Controller;
});

ルートでコントローラークラスを指定する場所に、この登録名を使用することができます。

ルートで指定されたクラスの場合、まずIoCに登録されていないかチェックされ、次にクラス名とし存在をチェックされます。ですから、使い道としてはテストのコントローラークラスからリダイレクトされる先のルートとして定義してある別のコントローラークラスをスタブにすることで、単体テストを行うためでしょう。

実際、コントローラーのテストは簡単です。コントローラー自身をインスタンス化できますし、ルートを指定してコントローラーの動作をテストすることも可能です。詳しくは公式ドキュメントをご覧ください。

参照:テストからコントローラーを呼び出す