Laravel の FormRequest を Data Transfer Object 的に使ってみた
はじめに
Data Transfer Object
Data Transfer Object(DTO)はデザインパターンの一種で、アプリケーションソフトウェアのサブシステム間でデータを転送するのに使う。
Data Transfer Object - Wikipedia
元々はサブシステム間のデータの受け渡しにおいて、受け手側の処理に最適化されたオブジェクトを作成 (送り手で保持しているオブジェクトを変換) する役割を持ったデザインパターンです。
そこから派生して、同一システム内の異なるレイヤー間でデータを受け渡す際にも、DTO 的なクラスが使われることがあります。
FormRequest
一方、Laravel の FormRequest と言えば、GET や POST でブラウザ (HTTPクライアント) から送られてきたデータを受け取って、バリデーションした後に、アクションメソッドにインジェクトされるクラスです。
以下のような使い方が一般的と思います。
使用例) 著者から本を検索する
public function index(SearchBookRequest $request)
{
$author = $request->get('author');
$books = Book::where('author', $author)->get();
// ...
}
検索条件が少なければ、Controller 側で上のようにクエリーを組み立てて、Eloquent な Model に直接渡してもいいんでしょうが、数が増えてくると大変なことになるので、できればこんな感じにシュッとできればいいなぁ、というのが、本記事を書いた動機です。
public function index(SearchBookRequest $request)
{
$params = $request->all();
$books = Book::where($params)->get();
// ...
}
フォーム要素とテーブルスキーマが完全に一致している上に、検索演算子がすべて =
であれば、上のやり方でできるわけですが、そうしたケースはめったになく、著者名は部分一致検索だったり、出版年月日は期間だったりして、色々ごにょごにょとやらないとクエリーが組み立てられません。
Controller を上のようなシンプルな構成に保ちつつ、Model 側でクエリーをつくるのもあまり複雑にしたくないので、キー名、比較演算子、値、の3つを FormRequest でつくって渡してみようという試みです。
フォームを扱うクラスに、SQL の情報が含まれてしまうのが、オブジェクト指向的にどうなの、というかんじもしますが、今回はお作法よりも利便性を取りたいと思います。
サンプルコード
<?php
namespace App\Requests;
use Illuminate\Support\Str;
class SearchBookRequest extends Request
{
private $comparators = [
'author' => 'like',
'published_at_from' => '>=',
'published_at_to' => '<=',
];
// authorize, rules は省略
public function params()
{
// 空の検索条件は取ってこない
$inputs = $this->intersect(['author', 'published_at_from', 'published_at_to']);
return $this->transform($inputs, $this->comparators);
}
private function transform(array $inputs, array $comparators)
{
$queryParams = [];
foreach ($params as $key => $value) {
$queryParams[] = $this->buildQueryParam($key, data_get($comparators, $key, '='), $value);
}
return $queryParams;
}
private function buildQueryParam($k, $comp, $v)
{
if ($comp === 'like') {
$v = "%{$v}%";
}
// 変換用メソッドがあればここで変換する
$transformer = 'transform' . Str::camel($k);
if (method_exists($this, $transformer)) {
return $this->$transformer($k, $comp, $v);
}
return [$y, $comp, $v];
}
// 例として単純なキー名(フィールド名)の変換をしたが、複雑な変換も可能
private function transformPublishedAtFrom($k, $comp, $v)
{
return ['published_at', $comp, $v];
}
}
これらをそのまま Eloquent な Model に渡すことができます。
public function index(SearchBookRequest $request)
{
$params = $request->params();
$books = Book::where($params)->get();
// ...
}
複雑な検索条件であっても、結果的に [$key, $comparator, $value]
の配列で返すことができれば、Controller を変更する必要はありません。
今回は SearchBookRequest という具象クラスに実装しましたが、もちろん、抽象クラスで実装して汎用的に使うこともできますし、もっと言えば、FormRequest で実装する必要もなく、DI を使って別のクラスに委譲することもできます。
また、サンプルコードでは、フォーム要素とDBのフィールドが一対一で対応していますが、ひとつのフォーム要素の値によって、複数の検索条件が導出されるようなパターンもあると思うので、そうしたケースのときどのように実装すればいいか、興味のある方は実装してみると面白いかもしれません。
まとめ
- FormRequest を DTO 的に使ってみたけど、使えそうだった
似たようなことをやられている方がいたら、ウチではこんな風にしてるよ、とかコメントいただけると助かります
Author And Source
この問題について(Laravel の FormRequest を Data Transfer Object 的に使ってみた), 我々は、より多くの情報をここで見つけました https://qiita.com/nunulk/items/39a8b4e347e183702be7著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .