LaravelでRepositoryパターンを実装する-入門編-
- 公開日
- 更新日
- カテゴリ:Laravel
- タグ:Laravel,Repository,Architecture

- LaravelでのRepositoryパターン実装
-
- 入門編
- 実践編
Laravel は自由度の高い PHP フレームワークですが、ノー設計プランでアプリケーション開発を行うと、規模が大きくなってきた時に多々辛みポイントが発生してきます。 肥大化したソースコードに整理されていないクラス群などなど、、機能追加の前に要リファクタリングなんて状況にもなりかねず、設計は大事な要素の1つだと思い知らされます。
今回は、設計手法の1つとして、Repository パターンでの実装を Laravel で行います。
開発環境
今回の開発環境は以下の通りです。
- MySQL 8.0
- PHP 7.2
- Laravel 5.8
今回は、Laravel で予め用意されている User のモデルやテーブルを用いて実装を行って行きます。マイグレーションを行い、予めテーブルを作成してください。
尚、Laravel のルートディレクトリを「laravel/」としています。
Repository パターンとは
Repository(リポジトリ)パターンは、ドメイン駆動型設計の一つとして導入されたもので、データソースへのアクセスの抽象化を提供するものです。
それぞれ異なる性質を持つデータソースに対して共通のインターフェイスを定義しそれをリポジトリとして実装する事で、 ビジネスロジックからデータアクセスを分離(疎結合)させ、機能としての拡張性・保守性を向上させる事ができます。
簡単に言うと、データソースへのアクセス・操作に関して「SELECT 」「INSERT 」「UPDATE 」「DELETE 」が思い浮かびますが、 例えば RDBMS と NoSQL で文法の違いがあるので、これらの操作の為に私達が書くコードにはそれぞれ違いがあります。 これらを共通のインターフェイスを持たせる事で、ビジネス側ではどのデータソースかを意識せずにデータ側を扱う事ができるようになる。という感じです。
これは機能を疎結合に保つことから、例えば仕様変更などが合った場合でも容易にデータアクセスを変更したり、追加したりする事ができます。 また、データ側をモック化する事でテストが容易に行えるなどの保守性も向上します。
今回の実装例
今回は、クエリビルダと Eloquent を用いたデータ取得機能を Repository パターンで実装し、切り替えてみようと思います。
尚、今回は最も基本的な「リポジトリの切り替え部分」をメインに行う為、詳細なアーキテクチャ部分に関しては一部簡略化しています。 本記事はあくまでも、リポジトリパターンの入門編として捉えていただければと思います。
Seeder&Faker を用いてデータ投入
今回は Laravel に最初から定義されている User テーブルを使うので、まずはデータを投入しておきます。 データ投入には、Faker を用いてダミーデータを挿入します。
まずはダミーデータの定義の為に Factory クラスが必要ですが、User モデルの場合は既に用意されています。
- laravel/database/factories/UserFactory.php
-
<?php use App\User; use Illuminate\Support\Str; use Faker\Generator as Faker; $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'remember_token' => Str::random(10), ]; });
今回はこのまま使用します。
尚、User 以外で行う場合は以下の artisan コマンドで Factory クラスを生成し、定義する必要があります。
php artisan make:factory HogeFactory
次に、シーダクラスを生成します。以下の artisan コマンドを叩きます。
# Laravel ルートディレクトリへ移動
cd /path/to/laravel
# シーダ生成
php artisan make:seeder UsersTableSeeder
# 実行結果
[demo@localhost laravel]$ php artisan make:seeder UsersTableSeeder
Seeder created successfully.
laravel/database/seeds 配下に UsersTableSeeder.php が生成されます。
laravel
├─ database
│ ├─ seeds
│ │ └─ UsersTableSeeder.php
- laravel/database/seeds/UsersTableSeeder.php
-
<?php use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { public function run() { } }
Seeder クラスを Factory クラスを用いて以下のように実装します。
- laravel/database/seeds/UsersTableSeeder.php
-
<?php use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { public function run() { factory(App\User::class, 10000)->create(); } }
今回は 10,000 件挿入します。
Seeder クラス定義したら、DatabaseSeeder クラスへの登録も忘れずに
- laravel/database/seeds/DatabaseSeeder.php
-
<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run() { $this->call(UsersTableSeeder::class); } }
そして Composer のオートローダを再生成します。
composer dump-autoload
ちなみにオートローダを再生成しないと、シーディングを実行した際に
「Class Database\Seed\UsersTableSeeder does not exist 」
と怒られるので、忘れずに叩きます。
それではデータを投入します。
# Laravel ルートディレクトリへ移動
cd /path/to/laravel
# Seeding 実行
php artisan db:seed
# 実行結果
[demo@localhost laravel]$ php artisan db:seed
Seeding: UsersTableSeeder
Database seeding completed successfully.
MySQL に入って確認します。
mysql> SELECT COUNT(id) FROM `users`;
+----------+
| count(id)|
+----------+
| 10000 |
+----------+
1 row in set (0.00 sec)
10,000 件挿入された事を確認しました。
Repository パターン実装
データの準備ができたので、Repository パターンを実装していきます。
まずはインターフェイスを定義します。以下のディレクトリ階層(適宜作成)で、UserDataAccessRepositoryInterface.php を作成します。
laravel
├─ app
│ ├─ Repositories
│ │ └── User
│ │ └─ UserDataAccessRepositoryInterface.php
どこに作るかはもちろん自由です。
- laravel/app/Repositories/User/UserDataAccessRepositoryInterface.php
-
<?php namespace App\Repositories\User; interface UserDataAccessRepositoryInterface { public function getAll(); }
今回は、先に挿入した 10000 件のデータ全件を取得するメソッドのみを宣言します。
続いて、このインターフェイスを実装します。
こちらも以下のディレクトリ階層(適宜作成)で、クエリビルダと、EloquentORM を用いたデータ取得クラスを作成します。
laravel
├─ app
│ ├─ Repositories
│ │ └── User
│ │ ├─ UserDataAccessRepositoryInterface.php
│ │ ├─ UserDataAccessQBRepository.php
│ │ └─ UserDataAccessEQRepository.php
今回はリポジトリの切り替えがメインなのでその先のサービスクラスとかはひとまず置いておき、両方ともシンプルにデータを全件取得し返します。
- laravel/app/Repositories/User/UserDataAccessQBRepository.php
-
<?php namespace App\Repositories\User; use DB; class UserDataAccessQBRepository implements UserDataAccessRepositoryInterface { protected $table = 'users'; public function getAll() { return DB::table($this->table)->get(); } }
- laravel/app/Repositories/User/UserDataAccessEQRepository.php
-
<?php namespace App\Repositories\User; use App\User; class UserDataAccessEQRepository implements UserDataAccessRepositoryInterface { public function getAll() { return User::all(); } }
実装できたら、これをサービスプロパイダに登録します。 記述先は AppServiceProvider でも良いのですが、「Repository 」を強調したいので、RepositoryServiceProvider を作成して登録します。
# Laravel ルートディレクトリへ移動
cd /path/to/laravel
# サービスプロパイダ作成
php artisan make:provider RepositoryServiceProvider
# 実行結果
[demo@localhost laravel]$ php artisan make:provider RepositoryServiceProvider
Provider created successfully.
artisan コマンドを叩いて生成したら、以下を定義します。
- laravel/app/Providers/RepositoryServiceProvider.php
-
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class RepositoryServiceProvider extends ServiceProvider { public function register() { $this->app->bind( \App\Repositories\User\UserDataAccessRepositoryInterface::class, \App\Repositories\User\UserDataAccessQBRepository::class ); } public function boot() { } }
まずはクエリビルダでのデータ取得クラスを登録しました。
忘れずに、設定ファイルにサービスプロバイダを追加します
- laravel/config/app.php
-
'providers' => [ /* . 省略 . . . */ // 追加 App\Providers\RepositoryServiceProvider::class, ],
一旦ここで、リポジトリクラス周りの実装は完了です。
コントローラ&ルーティング
最後にルーティングと、コントローラからの使用を前提に処理を定義します。 せっかくなので、全件取得した時のクエリビルダと EloquentORM でどれくらい時間がかかるのかを測ってもみたいと思います。
- laravel/app/Http/Controllers/SampleController.php
-
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Repositories\User\UserDataAccessRepositoryInterface AS UserDataAccess; class SampleController extends Controller { protected $User; public function __construct(UserDataAccess $UserDataAccess) { $this->User = $UserDataAccess; } public function index() { $start = microtime(true); $memory = memory_get_usage(); // ここでデータ取得を行う $data = $this->User->getAll(); $result = [ // どちらのリポジトリを使用しているかわかるように 'name' => get_class($this->User), // 実行時間 'time' => microtime(true) - $start, // 使用メモリ 'memory' => (memory_get_peak_usage() - $memory) / (1024 * 1024) ]; // 結果出力 var_dump($result); } }
インターフェイスを use し、コンストラクタインジェクションでメンバ変数へデータ取得機能を代入しています。 あとは、データ取得を行いつつ、時間とメモリを計測して結果をダンプする。といった流れです。
最後にルーティング
- laravel/routes/web.php
-
Route::get('/sample', 'SampleController@index');
動作確認とリポジトリの変更
一通り実装できたので、実際に動作を確認してみます。
現在はクエリビルダでのデータ取得を登録しているので、こちらでデータ取得が行われている事が確認できます。
ここで、データ取得を EloquentORM で行う方針変換があったとします。
リポジトリを切り替えて今度は EloquentORM でのデータ取得に切り替えてみます。
- laravel/app/Providers/RepositoryServiceProvider.php
-
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class RepositoryServiceProvider extends ServiceProvider { public function register() { $this->app->bind( \App\Repositories\User\UserDataAccessRepositoryInterface::class, // \App\Repositories\User\UserDataAccessQBRepository::class // ↓ 変更 \App\Repositories\User\UserDataAccessEQRepository::class ); } public function boot() { } }
ブラウザからアクセスして確認します。
今度は EloquentORM でのデータ取得クラスに切り替わっている事が確認できました。
まとめ
このようにして、Repository パターンを用いるとビジネスロジック側に変更を加えずに機能の切り替えが容易に行えるようになります。 また、先述した通り、ビジネス側とデータ側が分離されたので、テストも行い易くなっているはずです。
ちなみにせっかくなので触れると、クエリビルダの方が Eloquent より4倍弱早かったですね。
さて、今回は単純にリポジトリを切り替える事をメインにしてきました。その為、コントローラから直接リポジトリに対してデータ取得を行いましたが、適宜メイン処理を行うクラスなどを定義し、リポジトリのインターフェースはリポジトリと同階層ではなく上位(リポジトリがビジネスロジックに追従する形)に設置する事でリポジトリパターンとしての体裁が整います。その辺の事も、また別の機会に書きたいと思います。