彼らがLaravelとどのように連携しているかをコミュニティと共有したいと思います。 何か新しいことを学び、自分のデザインパターンで何が改善できるかを知りたいです。
私のコードでは、次のアプローチを使用しています。
Controller -> Service -> Repository -> Model
可能な場合は、一般的なガイドとして、SOLID原則に従うようにします。 したがって、これ以上の説明はせずに、コードに移りましょう。
ルート
Laravelリソースコントローラーを使用するのが好きです。 例として、ピザのリスト(インデックス)を含むページを作成しましょう。 また、ピザのサブオーダーページを表示する2つの例を追加しました。 (作成するページ、最後に注文を保存するページ)。
Route::resource('/pizzas', 'PizzaController', ['only' => [ 'index', ]]); Route::group(['prefix' => 'pizzas'], function() { Route::resource('/orders', 'Pizza\OrderController', ['only' => [ 'create', 'store', ]]); });
最終ルート:
GET /pizzas
App\Http\Controllers\PizzaController@index
GET /pizzas/orders/create
App\Http\Controllers\Pizza\OrderController@create
POST /pizzas
App\Http\Controllers\Pizza\OrderController@store
URLに基づいて名前空間を定義していることに気付くでしょう。 これを行う理由は、障害が発生した場合にデバッグする場所を見つけやすいためです。 さらに、私の意見では、これによりタスクが分離され、リソースコントローラーが処理する7つのアクションだけでコントローラーを制御できます。
- Pizza \ OrderController-ピザの注文処理のみを担当
- PizzaController-ピザ部品の処理を担当
コントローラー
上記を見るとわかるように、リソースコントローラーのLaravelドキュメントで提案されているように、7つのメソッドのみを使用しようとしています。
- インデックス()
- 作成()
- 店()
- 表示()
- 編集()
- 更新()
- 破壊()
理にかなっていると感じた場合、特定のコントローラークラス内で追加のメソッドを使用できる場合があることは認めますが、めったにそれをしようとしません。
コントローラーメソッドは、自動注入を使用してServiceクラスを読み込みます。 したがって、ピザリストページでは、PizzaServiceを使用してデータベースからすべてのピザを取得します。
public function index(PizzaService $pizzaService) { return view('pizza.index', [ 'pizzas' => $pizzaService->all(), ]); }
注:ビューは、名前空間と同じフォルダーパターンにも一致します。
サービス
サービスを使用して、アプリケーションのロジックを処理するのが好きです。 私にとってのサービスは、モデル(データベーステーブル)を使用したドメインドリブンまたは1対1の概念です。 サービスでよく使用する一般的なメソッドを処理する抽象クラスがあります。 (注:/コード例でドックブロックコメントを削除)
<?php namespace App\Services; abstract class BaseService { public $repo; public function all() { return $this->repo->all(); } public function paginated() { return $this->repo->paginated(config('paginate')); } public function create(array $input) { return $this->repo->create($input); } public function find($id) { return $this->repo->find($id); } public function update($id, array $input) { return $this->repo->update($id, $input); } public function destroy($id) { return $this->repo->destroy($id); } }
したがって、私のドメイン/モデルベースのサービスは次のようになります。
<?php namespace App\Services; use App\Repositories\PizzaRepository; class PizzaService extends BaseService { private $pizzaRepository; public function __construct(PizzaRepository $pizzaRepository) { $this->pizzaRepository = $pizzaRepository; } }
このPizzaServiceでは、実装しようとしているロジックに固有の独自のメソッドを追加できます。
$pizzaService->all()
リストページを続行するには、
$pizzaService->all()
が
$pizzaService->all()
のall()メソッドを呼び出します。上書きしないためです。
リポジトリ
私のコードのリポジトリは、基本的にEloquentを使用してデータベースへのデータの取得または書き込みを行うメソッドです。 リポジトリレベルを呼び出すことができるのはサービスのみです。 (私はこのアプローチを疑っていたが、今では常にそれに従うようにしている)。
<?php namespace App\Repositories; use Illuminate\Database\Eloquent\Model; abstract class BaseRepository { public $sortBy = 'created_at'; public $sortOrder = 'asc'; public function all() { return $this->model ->orderBy($this->sortBy, $this->sortOrder) ->get(); } public function paginated($paginate) { return $this ->model ->orderBy($this->sortBy, $this->sortOrder) ->paginate($paginate); } public function create($input) { $model = $this->model; $model->fill($input); $model->save(); return $model; } public function find($id) { return $this->model->where('id', $id)->first(); } public function destroy($id) { return $this->find($id)->delete(); } public function update($id, array $input) { $model = $this->find($id); $model->fill($input); $model->save(); return $model; } }
したがって、PizzaServiceによってロードされるPizzaRepositoryは次のようになります。
<?php namespace App\Repositories; use App\Models\Pizza; class PizzaRepository extends BaseRepository { protected $model; public function __construct(Pizza $pizza) { $this->model = $pizza; } }
また、サービスとリポジトリで、必要に応じて、デフォルトのメソッドを上書きして独自の実装を使用できることにも注意してください。 前の例のピザリストでは、BaseServiceがリポジトリ内のall()メソッドを呼び出したことを覚えています。 現在、PizzaRepositoryisはBaseRepositoryを上書きしないため、BaseRepositoryのall()メソッドを使用して、データベースからすべてのピザのリストを返します。
例として、BaseRepositoryメソッドを上書きする場合、私のメソッドの1つはストアドプロシージャを使用してデータを挿入するため、次のようにBaseRepositoryのcreateメソッドを上書きできます。
<?php namespace App\Repositories; use App\Models\Pizza; class PizzaRepository extends BaseRepository { protected $model; public function __construct(Pizza $pizza) { $this->model = $pizza; } public function create(array $input) { return $this->model->hydrate( DB::select( 'CALL create_pizza(?, ?)', [ $name, $hasCheese, ] ) ); } }
これは簡単な例ですが、今ではストアドプロシージャから水和した結果を返しています。
特性
特性のアイデアをコードに導入しました。 これは、リポジトリレイヤーの一部に並べ替えを構成する機能が必要であることがわかったときに発生しました(BaseRepositoryにはsortByとsortOrderの2つのプロパティがあります。したがって、Sortableフラグを作成しました。これらのプロパティでピザリストページを並べ替えることができます。
<?php namespace App\Repositories\Traits; trait Sortable { public $sortBy = 'created_at'; public $sortOrder = 'asc'; public function setSortBy($sortBy = 'created_at') { $this->sortBy = $sortBy; } public function setSortOrder($sortOrder = 'desc') { $this->sortOrder = $sortOrder; } }
それで、今、私がTraitを適用した私のサービスで、ソートを設定できます。 (以下の例では、コンストラクターで順序を設定していますが、このメソッドをサービスのカスタムメソッドで実行することもできます。)
<?php namespace App\Services; use App\Repositories\PizzaRepository; class PizzaService extends BaseService { private $pizzaRepository; public function __construct(PizzaRepository $pizzaRepository) { $this->pizzaRepository = $pizzaRepository; $this->pizzaRepository->setSortBy('sort_order'); } }
また、貪欲なロードを処理する方法を理解しようとしていくつかの困難がありました。 私はコントローラーにデータを返すという考えが気に入らず、それから遅延貪欲なロードを使用しました。 これは、データベースクエリの最適化にはあまり便利ではありませんでした。 Sortable Traitを作成したら、同様のRelationable Traitを作成することにしました。
<?php namespace App\Repositories\Traits; trait Relationable { public $relations = []; public function setRelations($relations = null) { $this->relations = $relations; } }
次に、with()メソッドをBaseRepositoryメソッドに追加しました:
public function all() { return $this->model ->with($this->relations) ->orderBy($this->sortBy, $this->sortOrder) ->get(); }
私のサービスを通じて、次のコードを任意のメソッドに追加できます(ordersはモデルリレーションシップメソッドです)。
$this->repo->setRelations(['orders']);
時間が経つにつれて、アプリケーションがあまりにも多くの特性で複雑になりやすいのではないかと心配していますが、今ではうまく機能しています。