パターン1(処理速度が遅め)
LaravelではPaginateメソッドを使用した場合、limitの制限がPaginateメソッドでのlimitが優先され、上書きされてしまう。
そのため、最大100件としたい場合でも、検索結果の全てが表示されてします。
そこで、ここではlimit制限をかけるために行ったカスタマイズ方法をお伝えする。
まずはよくある失敗事例として、次のコードを記載する。
$lists = Users::->where('id', '>', 10)->limit(100)->paginate(10);
こちらのソースでは、Usersテーブルからidカラム10以上のデータを100件取得し、かつページネーションをつける、という意味合いとなる。
この記述方法にて意図通りの動作が行われればいいものの、「limit(100)」が無視され、paginateメソッド内でのlimitに上書きされてしまい、結果としてwhere句で絞り込んだ全件を結果を返してしまう。
次に、カスタマイズ方法を記載する。カスタマイズを行う場合、Paginateメソッドの元ソースを変更するため、アップグレード等には再度調整を行う等、注意が必要となる。
変更を行うファイルは、
「vendor\laravel\framework\src\Illuminate\Database\Eloquent\Builder.php」
となる。
「Builder.php」ファイルは次の通り。
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$total = $this->toBase()->getCountForPagination();
$perPage = ($perPage instanceof Closure
? $perPage($total)
: $perPage
) ?: $this->model->getPerPage();
$results = $total
? $this->forPage($page, $perPage)->get($columns)
: $this->model->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
これを、次の通りに変更する。
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $limit = 10000000)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$total = $this->toBase()->getCountForPagination();
if($total > $limit) $total = $limit;
$perPage = ($perPage instanceof Closure
? $perPage($total)
: $perPage
) ?: $this->model->getPerPage();
$results = $limit
? $this->forPage($page, $perPage)->limit($perPage)->get($columns)
: $this->model->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
変更箇所としては、パラメータに「$limit = 10000000」を追加。
「$total = $this->toBase()->getCountForPagination();」の後に、
「if($total > $limit) $total = $limit;」を追加する。
理由として、設定したlimit以上の場合、limitで上書きを行うためのもの。
「$results = $total」を「$results = $limit」に変更する。
「? $this->forPage($page, $perPage)->get($columns)」を、
「? $this->forPage($page, $perPage)->limit($perPage)->get($columns)」に変更する。
この設定により、limitをかけることが可能となる。
次に使用方法を記載する。
例として、検索パラメータがある場合を想定したものとする。
コントローラは次のとおり。
// パラメータ
$keyword = $request->query('k');
$params = [
'k' => $keyword
];
// 検索条件
$where[] = ['name', 'like', sprintf('%%s%', $keyword];
// 検索結果取得
// パラメータはpaginateメソッドの通常の設定値と、最後にlimitのための100を追加
$lists = Users::->where($where)->paginate(10, ['*'], 'page', null, 100);
// viewに渡す
return view('search', compact('lists', 'params'));
viewは次のとおり。
<!--
検索結果を出力
-->
@if(!empty($lists) && $lists->count())
@foreach($lists as $key => $col)
<div>{{ $col->name }}</div>
@endforeach
@endif
<!--
ページネーションを出力
appendsメソッドを使用し、検索パラメータ「$params」を設定する。
-->
@if(!empty($lists) && $lists->count())
{{ $lists->appends($params)->links() }}
@endif
これにより、paginateにてlimitをかけることが可能となった。
パターン2(処理速度が早い)
パターン1の方法では、「Builder.php」にて取得件数を「$this->toBase()->getCountForPagination();」にて取得を行っている。
これは、全件データを取得するためデータ量が多い場合、速度の低下につながる。
そこで、countを使わず取得する流れとする。
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null, $limit = 10000000, $limitNew = false)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$this->setQuery($this->getQuery()->limit($limit));
if($limitNew) $total = count($this->getQuery()->get());
else $total = $this->toBase()->getCountForPagination();
if($total > $limit) $total = $limit;
$perPage = ($perPage instanceof Closure
? $perPage($total)
: $perPage
) ?: $this->model->getPerPage();
$results = $limit
? $this->forPage($page, $perPage)->limit($perPage)->get($columns)
: $this->model->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
変更箇所としては、パラメータに「$limit = 10000000」の後、「$limitNew = false」を追加。
「$this->toBase()->getCountForPagination();」を
「
if($limitNew) $total = count($this->getQuery()->get());
else $total = $this->toBase()->getCountForPagination();
」
に変更する。
取得方法は「$lists = Users::->where($where)->paginate(10, [‘*’], ‘page’, null, 100, true);」となる。
「true」の場合、countにてlimit後のデータを取得し、「false」の場合、これまで通りとなる。
これにより、高速にデータを取得することが可能となった。
問題点として、limitのデータ量が多くなる場合、メモリーオーバーが発生し、500エラーを発生してしまう。
そのため、limit数が1万未満程度であれば高速化に有効な手段である、とする。