こんにちは。クオンです。
夏が来て、最近は暑いですね。
今回はLaravelリポジトリパターンについて書こうと思います。
Laravel4.2のドキュメントはこちらでご参考ください。
Eloquent ORM
Laravelではデータベースの接続、そしてクエリの実行に関してシンプルな解決策が提示されています。
データベース操作で、Eloquentがよく使われています。
Eloquentとは
- Laravelに含まれるActiveRecordパターンの実装
- たくさんの便利なメソッドが用意され、シンプルでパワフルなインターフェースを備えている
Eloquentを使用すると、データベースへのアクセスがとても簡単になります。
以下はControllerまたはServiceクラスからの、データベースアクセスの記述例になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// コメントはMySQLで書いています。 // select * from users; $users = User::all(); // UserクラスはusersテーブルのEloquentモデルクラス // select * from users where user_id = 1 limit 1; $user = User::find(1); // select * from users where role = 1 order by username; $user = User::whereRole(1)->orderBy('username')->get(); // 情報更新 $user = new User(); $user->role = 2; $user->save(); |
このような書き方は簡単でプログラムを早く作成する事ができます。
しかし次のような問題があります。
- データベースエンジンの変更があった場合、変更が大変
- 単体テストがしにくい
いつか、例えばプロジェクトが大きくなったら、MySQLではなくて、やっぱりOracleにする場合、上記の例のように、データベースアクセスを記述していたり、クラスを直接newで生成していると、変更箇所が膨大になります。
Eloquentモデルをモック化できなくて、単体テストがしにくいですね。
なぜEloquentモデルをモック化できないのか?
ここでファサードについて少し話しましょう。(詳しくはこちらです)
ファサードはIoCコンテナに登録されたクラスを静的メソッド(Class:methodという形式)で呼び出すことを可能にしています。
1 |
Event::fire('foo', array('name' => 'Dayle')); |
Eventクラスが使用された場合に、エイリアスであるIlluminate\Support\Facades\Eventクラスが実際に動作しています。
ファサードはモックオブジェクトフレームワークであるMockeryと統合されていて、ファサードを簡単にモック化できます。
1 2 |
// ファサードをモック化 Event::shouldReceive('fire')->once()->with('foo', array('name' => 'Dayle')); |
しかし、Eloquentはファサードを継承していないので、このように簡単にモック化できません。
上記の問題を解決するために、リポジトリパターンを使用します。
リポジトリパターン
リポジトリパターンとは
リポジトリパターンとは、データの取得、保持、元になるモデルへのマップを行うロジックを、そのデータを処理するビジネスロジックから分離します。
Laravel以外でも、リポジトリパターンは良く使われています。
Laravelリポジトリパターンの実装
まずはモデルごとに、Repositoryのインターフェースと実装クラスを作ります。
インターフェースを使用すると、コードは簡単に理解でき、メンテナンスできるようにもなります。そして、実装クラスを差し替えることができます。
以下はサンプルとして、UserモデルのUser Repositoryです。
UserRepositoryInterface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
interface UserRepositoryInterface { /** * @param integer $userId * @return User|null */ public function find($userId); /** * @return Collection User */ public function all(); /** * @param integer $role * @return Collection User */ public function getByRole($role); } |
EloquentでUserRepositoryInterfaceを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class UserRepository implements UserRepositoryInterface { public function find($userId) { return User::find($userId); } public function all() { return User::all(); } public function getByRole($role) { return User::whereRole($role)->orderBy('username')->get(); } } |
次はIoC(Inversion of Container)にRepositoryを登録します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// RepositoryProvider /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton( 'UserRepositoryInterface', 'UserRepository' ); ........... // 他のRepository } |
このようにContainerに登録すると、UserRepositoryInterfaceインターフェースがインジェクトされるときにUserRepositoryクラスが使用されます。
リポジトリを使用する
依存注入(DI)を使って、コントローラーまたはサービスのコンストラクタの引数にUserRepositoryInterfaceを指定します。
1 2 3 4 5 6 7 8 9 |
class UserController extends Controller { /** @var UserRepositoryInterface */ public $userRepository; public function __construct(UserRepositoryInterface $userRepository) { $this->userRepository = $userRepository; } |
このようにすると、UserControllerクラス生成時にUserRepositoryインスタンスを生成し、UserControllerクラスのコンストラクタに注入されます。
次はUserRepositoryを使いましよう。
1 2 3 4 5 6 |
// UserController public function index() { $user = $this->userRepository->find(); $userList = $this->userRepository->all(); } |
データベースリポジトリはコントローラクラスのプロパティーの一つとして、使用されます。
これでリポジトリパターンの実装は完成です。
リポジトリパターンを使用するとデータベースの変更があった場合、変更を簡単に実装できます。Repositoryクラス(例えばUserRepositoryクラス)で実装ロジックを変更すれば対応可能です。また単体テストを簡単に行う事ができます。
1 2 |
$mockUserRepository = \Mockery::mock('UserRepository[all]'); $mockUserRepository->shouldReceive('all')->andReturn($mockUserList); |
一方、リポジトリパターンを実装すると、プロジェクト構造が複雑になり、工数も増えますので、必要がない場合は、(例えば小さいプロジェクトなど)、リポジトリパターンを使わないほうが良いと思います。
最後まで読んでいただき、ありがとうございました。
これからも宜しくお願い致します。
システム部で開発させて頂きます。学んだこと、気になることを書こうと思います。