LaravelのMacroを使ってBuilderに機能を追加する方法
どうも、くずき(@kzkohashi)です。
先日、正しいJSON API
のフォーマットにするために以下のライブラリを導入した。
paginateは正しいクエリーパラメーターを取っていない
恥ずかしながら最近知ったのだが、Eloquent
で使用しているpaginate
は、完璧なJSON API
のフォーマットに対応していない。
ページ番号で使われるpage=2
などのクエリパラメーターは、正しくは、page[number]=2
にすべきみたいだ。加えて、ページ番号だけでなく、そのページにどれくらい表示させたいかなどはpage[size]=20
で表現する。
なので、導入前と導入後ではこうなる。
導入前
xxx.com/xxx?page=2&size=20
// sizeに関しては独自実装
導入後
xxx.com/xxx?page[number]=2&page[size]=20
さらにきになる方は以下を参照。
Note: JSON API is agnostic about the pagination strategy used by a server. Effective pagination strategies include (but are not limited to): page-based, offset-based, and cursor-based. The page query parameter can be used as a basis for any of these strategies. For example, a page-based strategy might use query parameters such as page[number] and page[size], an offset-based strategy might use page[offset] and page[limit], while a cursor-based strategy might use page[cursor].
JSON API — Latest Specification (v1.0)
ライブラリの実装を見て見る
ここからが本題。
このライブラリを導入し、設定ファイルを書くとjsonPaginate()
が使えるようになる。
YourModel::where('my_field', 'myValue')->jsonPaginate();
どうやって実装したんだろうとコードをみて見るとBuilder::macro
によって独自の関数を追加できるみたいだ。
<?php ... Builder::macro(config('json-api-paginate.method_name'), function (int $maxResults = null, int $defaultSize = null) { $maxResults = $maxResults ?? config('json-api-paginate.max_results'); $defaultSize = $defaultSize ?? config('json-api-paginate.default_size'); $numberParameter = config('json-api-paginate.number_parameter'); $sizeParameter = config('json-api-paginate.size_parameter'); $size = request()->input('page.'.$sizeParameter, $defaultSize); if ($size > $maxResults) { $size = $maxResults; } return $this->paginate($size, ['*'], 'page.'.$numberParameter) ->setPageName('page['.$numberParameter.']') ->appends(array_except(request()->input(), 'page.'.$numberParameter)); });
これはLaravel
のMacro
という機能で、Laravel
が提供している機能に独自のメソッドなどを拡張実装できるみたい。
Builder
以外には、HTML
, Response
などを拡張できる。
他にどう使えるか?
例えば、Eloquent
が返すModel
ではなく、独自に実装したEntity
を返すcollection
を用意するとか
<?php Builder::macro('toDomainCollection', function () { /** @noinspection PhpUndefinedMethodInspection */ return $this->get()->map(function (Domainable $model) { return $model->toDomain(); }); });
レスポンスである条件の時に決まった内容などを送る時とか
<?php Response::macro('notPermitted', function ($type = null) { if (strtolower($type) === 'success') { return $this->json(['data' => []], 200); } return $this->json(['data' => []], 403); });
今まで作ってたやり方を見直すだけでも色々ありそーだなーと思います。