Laravel3、Eloquentモデルを置き換える

タグ: Laravel3   テスト志向   高速開発  

LaravelのEloquntモデルの特徴は使いやすさです。

例えば'apples'テーブルの最初のレコードを引き出すには、まずEloquentモデルを定義し:

class Apple extends Eloquent { }

この定義したモデルを通じてアクセスします。

$apple = Apple::find(1);
echo $apple->name;

読みやすく、バリバリ書けます。小規模で単純なアプリを作成するなら、これが正解でしょう。

ですが、弱点もあります。まず、上のコードではAppleクラスを置き換えられないことです。ハードコードしていますからね。コードが複雑になり、複雑なテストを行いたい時にスタブに置き換えられません。(大規模なシステムを構築するならば、直接Eloquentをロジックのコードに書くのは避けましょう。アクセスのための中間クラスを挟むほうが理想でしょう。)

(追記:この記事を書いた後、すぐに気付いたのですが、Laravelのクラスエイリアスの仕組みを利用すれば、Eloquentモデルも置き換え可能です。Autoloadeクラスで通常のクラスのローディング処理の前に、エイリアス名が登録されているかがチェックされるコードになっているからです。もちろん、この方法は現在のAutoloadクラスの実装に依存しています。仕様で決まっていない部分を利用する裏技ですので、可能ですが非推奨です。)

そして、事前に定義していたAppleクラスのように、デフォルトの規約に従う場合、いちいちEloquentクラスを作成するのは煩わしくなります。規約に従っているのだから、クラスを定義しなくても使えるようにしたいですよね。

さらに、せっかくLaravelにはIoCコンテナが備わっていますが、それが活用できません。Laravel4ではIoCコンテナでインスタンスを取得すれば、登録していないクラスでも、そのオリジナルな名前で自動的にインスタンスを生成してくれるため、非常に使いやすくなっています。それと似たようなことをLaravel3でもやりたいと思いました。

そこでひと工夫です。

まず、Baseモデルを作成します。

class Base extends Eloquent
{
    public function setTabel($name)
    {
        static::$table = $name;
    }
}

Laravel3ですがキャメル記法を使用しているのはご愛嬌と言う事で見逃してください。:D

このクラスでは実際にアクセスするテーブル名のセッターを追加しています。もちろん、これを拡張することで、各クラスに共通のコードをこのクラスに置けるようにもなりますね。

続いて、Modelクラスを作成します。(Eloqeuntの本当のクラス名もModelですが、こちらは別名Eloquentで定義しているため、バッティングはしません。)

class Model
{
    public static function eloquent( $name )
    {
        If( IoC::registered( $name ) )
        {
            return IoC::resolve( $name );
        }
        elseif( class_exists( $name ) )
        {
            return new $name;
        }
        else
        {
            $model = new Base;
            $model->setTabel( strtolower( Str::plural( class_basename( $name ) ) ) );

            return $model;
        }
    }
}

(修正:多分、無意識にCtrl-Z叩いて、コードの内容を古くしたものを記載していました。Baseで作成したsetTableを使い、tableプロパティーをセットするのが正解です。)

コードが示す通りの動きを行います。IoCに指定されたモデル名のクラスが登録されていれば、それを生成します。指定されたクラスが実存しているいるなら、それを生成します。実存していなければBaseクラスのインスタンスを生成し、テーブル名を指定されたクラス名の複数形に設定します。

実際の使用方法は以下の通りです。

    $appleModel = Model::eloquent( 'Apple' );
    $apple = $appleModel->find( 1 );
    echo $apple->name;

Eloquentモデルのメソッドはスタティックで呼び出すことも可能ですが、上記のようにインスタンスから呼び出すことも可能です。コードの簡潔さはやや失われますが、ライブラリーやフレームワークを使用する場合にはよくあるパターンですので、馴染み深い人も大勢いらっしゃるでしょう。

上の例ではAppleという名前がIoCに登録されていれば、IoCコンテナがインスタンスを生成します。IoCコンテナに登録されていなければ、Appleクラスが存在するかチェックします。存在していれば、そのインスタンスが生成されます。存在していなければBaseモデルのインスタンスですが、アクセス先のテーブル名にはapplesが指定されていますので、問題なくアクセスできます。

以下の良くあるだろうシナリオで活用できるでしょう。

  1. Laravelの規約に従ったクラス名とテーブル名を採用し、動作もデフォルトで良いならば、Appleクラスを定義せず、お手軽にapplesテーブルにアクセス。
  2. セッターを定義したり、タイムスタンプの自動更新を無効にしたり、リレーションを定義する場合は、Appleクラスを定義する。
  3. コードが混んできて、デバッグに手間取ったり、テストしたい時は、IoCコンテナで名前Appleに対してスタブの生成コードを定義し、クラスを置き換える。
IoC::register('Apple', function() {
    return new Stub\Apples\AppleStubNoRecodeReturn;
}

これで、お気軽さを保ちつつ、IoCで置き換えもできるようになりました。めでたし、めでたし。