Larave4、IoCの登録とサービスプロバイダー

タグ: Laravel4  

IoCコンテナはとても便利になりました。コアにも積極的に使用されています。

Laravel3の時は、どこでIoCの登録コードを書けば良いのか迷いましたが、Laravel4では推奨される方法がドキュメントに示されています。それを紹介します。

この記事を書いている時点ではまだベータ4です。5月の正式リリース版では変更になる部分もあるかも知れません。ご了承ください。

IoCコンテナの基本的な使用方法

IoCコンテナを思いっきり短く説明すれば、「クラス名をハードコードすると置き換えが出来ないので、テストもしづらいし、拡張性に劣ってしまう。だから代わりにコンテナ用の名前で定義しておいて、その名前でコーディングする。実行前のどこかで名前と実際のクラスを結びつける定義をおこなう。」となります。

Laravel4のドキュメントでは、IoCの登録名を「タイプ」と読んでいます。

いくつかの使用方法があります。いずれの場合も、明示的にコンテナに対しタイプと、実際のクラスを結び付けなくても、タイプ名と同じ名前のクラスがあれば、それが生成されます。

例えば以下のコードではOrangeのインスタンスが$orangeに取得できます。newの代わりに使用しておけば、いざという時には置き換え可能になります。

class Orange {}

$orange = App::make('Orange');

クラスを置き換えるなら、タイプと実際のクラスを結びつけます。

App::bind('Orange', 'Apple');

$orange = App::make('Orange');

これで、$orangeにはAppleクラスのインスタンスが入ることになります。

クラスとの結びつきにはクロージャーを利用し、ロジックを入れることもできます。例えば以下のコードでは、ローカル動作環境とそれ以外でクラスを自動的に使い分けます。

App::bind('Orange', function(){
    if ( App::environment() == "local" )
    {
        return new Orange;
    }
    else
    {
        return new Apple;
    }
});

このように明示的なクラスの取得をしなくても、IoCのパターンではおなじみなコンストラクターによる取得もサポートされました。

class Apple {
    praivate $orange;
    public function __construct(Orange $orange)
    {
        $this->orange = $orange;
    }
}

$apple = new Apple;

この場合も、明示的な結びつきの定義がない場合、タイプヒントが指名しているOrageクラスがあれば、それが自動的にnewされ、インスタンスが渡ってきます。

ただし、タイプヒントがインターフェイスの場合、newできませんので自動的な結びつきは当然動作できません。明示的にbindする必要があります。

IoCコンテナのタイプとクラスの定義

ですから、大抵の場合コード中でnew クラス名としていた場所をApp::make('クラス名')とすれば、自動的に依存性注入の恩恵を受けることができます。

クラス名とタイプ名を原則同じ名前にすると、上記の通りほとんど明示的にbindしなくても済みます。

しかし、大きなプロジェクトでタイプ名とクラス名は分けてきちんとbindしたいとか、一部のクラスの置き換えが必要だなんて場合、どうするかです。

Laravel4は、準備コードを用意するクラスとして、サービスプロバイダーの考え方を取り入れています。これを利用し、bindをする方法がドキュメントには書かれています。しかし、ほんの一つだとか、一時的であれば、startディレクトリーのglobal.phpなどのスタートアップファイルに書き込んでも間違いではありません。基本、フレームワークですから、使用方法は自由です。

サービスプロバイダーを使用する場合、まずサービスプロバイダーを作成します。今回SampleProjectというPSR-0規約を採用したプロジェクトにしましょう。プロジェクトフォルダー直下にSampleProjectServiceProvider.phpを作成します。

<?php

namespace SampleProject;

use Illuminate\Support\ServiceProvider;

class SampleProjectServiceProvider extends ServiceProvider
{
    public function register()
    {
        \App::bindIf('Apple', 'SomeNameSpace\\Apple');
        \App::bindIf('Orange', 'SomeNameSpace\\Orange');
        \App::bindIf('UserValidator', 'SampleProject\\Services\\Validators\\UserValidator');
        \App::bindIf('UserRepo', 'SampleProject\\Repositories\\UserRepo');
    }
}

bindIfはそのタイプ名に対する定義が既に行われている場合は、設定を行いません。未設定の場合のみ定義するメソッドです。例えば、単体テスト時は、頻繁にこの定義を変更する必要がありますので、テストコードの中でbindメソッドで結びつけるほうが便利です。そうした処理はサービスプロバイダー中のコードの前で行われるかも知れませんし、後かも知れません。

bindIfで定義しておけば、そうした置き換えの後で実行された場合でも、bindメソッドのように上書きしてしまうことがありません。ですからサービスプロバイダー中ではbindIfを使用するほうが良いでしょう。

次に、このサービスプロバイダーをリクエスト開始時に読み込むようにapp/config/app.phpで指定します。

'providers' => array(

        'Illuminate\Foundation\Providers\ArtisanServiceProvider',
        'Illuminate\Auth\AuthServiceProvider',
            .
            .
            .
        'Illuminate\View\ViewServiceProvider',
        'Illuminate\Workbench\WorkbenchServiceProvider',

        // 追加
        'SampleProject\SampleProjectServiceProvider',

    ),

これでリクエストを受け取り、処理を開始する時に、タイプとクラスが自動的に結び付けられます。

まあ、前述の通り本格的にIoCを活用するなら、こうなるでしょう。逆にちょっとだけ使用したい場合、これは面倒臭いですね。ですから、自分の作成するシステムの大きさに合わせて、活用ください。

サービスプロバイダー自身は、IoCの登録以外、自分のプロジェクトの準備を整えるコードでしたら、何でも書き入れることができます。IoCの登録だけに使用を限定されているわけではありません。自由に使用しましょう。

現状の問題点

Laravel3の時はタイプに対するクラスが登録されているか調べるhadメソッドが存在していました。現状のベータ4時には、同様のメソッドは存在していません。

存在していないタイプ名でmake()をかけると例外が発生するため、事前に調べたくても、調べようがありません。

Laravel3でも4でも、IoCの解決にはReflectionクラスを使用しており、存在しないタイプ名を指定した場合、ReflectionExceptionが発生します。そのため、この例外をcatchすれば、対処できるでしょう。