LaravelAdminのシャーディング対応



　本記事は、サムザップ Advent Calendar 2020 #2 の12/6の記事です。

　LaravelAdminは、いわゆる管理画面のフレームワークです。(公式: https://laravel-admin.org/docs/en/)

　シンプルながらデザインが整っていて、いちから作るよりはよい機能がそろっています。また、CRUDで作られていて、定型的な表示や操作ならモデル指定ぐらいで作れたりします。

　一方で、ゲーム開発のサーバでは負荷分散のためにシャーディングをしていることが多く、データベースとテーブルを複数組み合わせてひとつのデータを示していることがよくあります。
　LaravelAdminでは、そのままこれらのデータを扱うことができません。
　ここではそんなLaravelAdminでシャーディングしたデータを扱うためのいくつかの方法を述べてみます。

### 何が問題?

　LaravelAdminではGrid、Formなどいくつかの表示や操作の仕組みを提供していて、それらのクラスに対してモデルを渡したり表示内容などを記述するといった作り方をしています。
　渡すモデルは、あらかじめ生成したモデルを渡すことができるので、シャーディングされたDBやテーブルをシャーディングキーで特定することが事前にできれば、そのシャーディング先を示すモデルを扱うことはできます。
　ただシャーディングされたデータをそのままGridに渡すと、全件表示されてしまいます。たとえばユーザーIDをシャーディングキーにしているものなら、ユーザーIDを指定しても、ほかのユーザーのデータも表示されてしまいます。
　ほかにも条件による絞り込み機能など、サブ的な仕組みには、シャーディングキーを呼び出し元と揃えたい場合があり、シャーディングキーをどうやって渡すかがポイントになりがちです。


###シャーディングに対応したgridの作成

　Gridは便利な機能ですが、そのままでは扱えないので、LaravelAdminのコードを改造することにしました。
　考え方として、シャーディングキーをあらかじめフィルターのように使えば、Gridの表示の際、シャーディングキーに合わせた表示がされるはずです。この仕組みを「デフォルトのフィルター」としてGridに用意してやればいいわけです。
　vendor/encore/laravel-admin/src/Grid.phpに以下のコードを追加します。

```Grid.php
/**
 * default filter key
 * @var string
 */
protected $defaultFilterKey;

/**
 * default filter value
 * @var mixed
 */
protected $defaultFilterValue;


/**
 * Add default filter.
 *
 * @return void
 */
public function addDefaultFilter($key, $value)
{
    $this->defaultFilterKey = $key;
    $this->defaultFilterValue = $value;
}
```

　同じGrid.phpのapplyQuery()では、フィルタに基づく条件の設定などを行っています、ここでdefaultFilterKey、defaultFilterValueをwhereして、検索条件に加えます。


```Grid.php
/**
 * @return array|Collection|mixed
 */
public function applyQuery()
{
    if (!empty($this->defaultFilterKey)) {
        $this->model()->where($this->defaultFilterKey, $this->defaultFilterValue);
    }

    $this->applyQuickSearch();

    $this->applyColumnFilter();

    $this->applyColumnSearch();

    $this->applySelectorQuery();
}
```

　使い方は次の通りです。ここでは$userIdがシャーディングキーになります。

```
$hogemodels = new HogeModel();

//シャードをセット。DBが決まる
$hogemodels->setShard($userId);

//Gridの初期化コールバックでシャードキーを設定。表示を$userIdのものに指定
Grid::init( function(Grid $grid) use ($userId) {
    $grid->addDefaultFilter('user_id', $userId);
});

return Admin::content(function (Content $content) use ($hogemodels, $userId) {
    //Gridの利用
    $content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) {
        $grid->user_id('ユーザーID')->sortable();
    }));
});
```


### シャーディングキーを渡して表示条件指定を行う

　フィルター機能にはhiddenフィールドを記述できる仕組みがあるので、それでシャーディングキーを渡すようにすると、うまく呼び出せます。
　以下のようにfilterのコールバックでhidden()を記述します。$userIdがシャーディングキーです。

```
public function resultview(Request $request)
{
    $userId = $request->user_id;
    $hogemodels = new LogTrophies();
    $hogemodels->setShard($userId);

    Grid::init( function(Grid $grid) use ($userId) {
        $grid->addDefaultFilter('user_id', $userId);
    });
    return Admin::content(function (Content $content) use ($hogemodels, $userId) {
        $content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) {
            $grid->user_id('ユーザーID')->sortable();
            
            $grid->perPages([100, 200, 500, 1000]);
            $grid->filter(function ($filter) use ($userId) {
                $filter->disableIdFilter();
                $filter->hidden('user_id', $userId);
                $filter->equal('type', 'type');
                $filter->equal('key', 'key');
                $filter->equal('value', 'value');
            });
        }));
    });
}
```


### シャーディングキーを渡してEditフォームを利用する

　Editフォームにはhiddenフィールドの記述方法がないので、シャーディングキーを渡すのにちょっと頭を使います。
　Editフォームを呼び出すために使うURLには、呼び出し元のURLが使われます。そこで$grid->setResourceでシャーディングキーを混ぜたURLにしてやることで、シャーディングキーを渡せます。
　大元のGridで以下のようにして


```
$content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) {
    $grid->setResource('/admin/hoge/resultview/' . $userId); //Edit Action用にユーザーID渡すため
    $grid->user_id('ユーザーID')->sortable();
}));
```


routes.phpは以下のようにシャーディングキーであるUserIdと、テーブルのユニークIDであるidを両方取るように記述します。

```routes.php
$router->get('/hoge/resultview/{userId}/{id}/edit', 'HogeController@edit');
```

Editの受け側には、userId、idの2つの引数を取るように記述します

```
public function edit($userId, $id)
{
    $hogemodels = new UserBirthdays();
    $hogemodels->setShard($userId);
    Grid::init( function(Grid $grid) use ($userId) {
        $grid->addDefaultFilter('user_id', $userId);
    });

    return Admin::content(function (Content $content) use ($hogemodels, $userId) {
        $content->header('hoge情報');
        $content->description('編集');
        $content->body(
            Admin::form($hogemodels, function (Form $form) use ($hogemodels, $userId) {
                $form->display('ユーザーID')->default($userId);
                $form->setaction('/admin/hoge/modification');
            })
        );
    });
}
```


### まとめ

　LaravelAdminは便利なフレームワークですが、便利であるがゆえに仕組みが決まっているため、最初は作法がわからずとっつきにくいかもしれません。でも、いろいろコードを調べると、便利なものが多くあります。ぜひいろいろ試してみてください。

　明日は @hiroki_shimada の記事です。