目次
Laravelの記事一覧は下記
PHPフレームワークLaravelの使い方
Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています
前提条件
eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています
Laravelでデータベースを操作する(クエリビルダ編)
本記事は上記が完了している前提で書かれています
本記事は上記で作成したフォルダとファイルを使用します
サービスクラス修正
ここで修正するサービスクラスは
Laravelでデータベースを操作する(クエリビルダ編)
で作成したものです
(1) /sample/app/Services/Interfaces/Table2Service.php修正
selectメソッドの引数修正
引数に$limit = null, $page = null, $sort = null
追加
‥‥
public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null);
‥‥
(2) /sample/tests/Services/Impl/Table2ServiceImpl.php修正
selectメソッドの引数修正
引数に$limit = null, $page = null, $sort = null
追加
‥‥
public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null)
{
}
‥‥
(3) /sample/app/Services/Impl/Table2ServiceImpl.php修正
selectメソッド修正
‥‥
public function select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit = null, $page = null, $sort = null)
{
$columns = [
'table1.id as table1_id',
'table1.varchar_col as table1_varchar_col',
'table1.int_col as table1_int_col',
'table1.datetime_col as table1_datetime_col',
'table1.date_col as table1_date_col',
'table1.time_col as table1_time_col',
'table2.id as table2_id',
'table2.varchar_col as table2_varchar_col',
'table2.int_col as table2_int_col',
'table2.datetime_col as table2_datetime_col',
'table2.date_col as table2_date_col',
'table2.time_col as table2_time_col',
'table3.id as table3_id',
'table3.varchar_col as table3_varchar_col',
'table3.int_col as table3_int_col',
'table3.datetime_col as table3_datetime_col',
'table3.date_col as table3_date_col',
'table3.time_col as table3_time_col',
];
$db = DB::table('table2')
->leftJoin('table1', 'table2.table1_id', '=', 'table1.id')
->leftJoin('table3', 'table2.id', '=', 'table3.table2_id')
->select($columns);
if (!is_null($id)) {
$db->where('table2.id', '=', $id);
}
if (!is_null($varchar_col)) {
$db->where('table2.varchar_col', 'like', '%'. addcslashes($varchar_col, '\\_%') . '%');
}
if (!is_null($int_col)) {
$db->where('table2.int_col', '=', $int_col);
}
if (!is_null($datetime_col)) {
$db->where('table2.datetime_col', '=', $datetime_col);
}
if (!is_null($date_col)) {
$db->where('table2.date_col', '=', $date_col);
}
if (!is_null($time_col)) {
$db->where('table2.time_col', '=', $time_col);
}
if (!is_null($sort)) {
$sortCol = ['table2.id',
'table2.varchar_col',
'table2.int_col',
'table2.datetime_col',
'table2.date_col',
'table2.time_col'];
$db->orderBy($sortCol[$sort]);
}
if (!is_null($page) && !is_null($limit)) {
$recordList = $db->paginate($limit, $columns, 'page', $page);
} else {
$recordList = $db->get();
}
return $recordList;
}
‥‥
大きな修正は$db->paginateメソッドを追加したことです
paginateメソッドの
第1引数は1ページに表示する件数です。
第2引数はselectするカラムです。
今回は
DB::table('table2')
‥‥
->select($columns);
で取得するカラムを指定しているので意味ないです
第3引数はページ番号が送信されてくるHTTPリクエストパラメーター名です。
第4引数はページ番号です。select文ではこの引数-1がoffsetになります
第2引数から第4引数は省略できます。省略した場合、第2引数は[*]、第3引数は'page'、第4引数はnullになり、ページ番号は第3引数名のHTTPパラメーター値になります(第3引数、第4引数を省略すれば、pageという名前のHTTPパラメーター値となる)
今回はクエリビルダを使いましたが、paginateメソッドはEloquentでも使えます
Laravelでデータベースを操作する(Eloquent編)
でEloquentを使うサービスクラスを作成しました。そこではgetメソッドをつかってselectしましたが、getメソッドの部分を今回と同じpaginateメソッドに変更すれば、Eloquentでページネーションできます。クエリビルダのpaginateメソッドもEloquentのpaginateメソッドも引数は同じです
フォームリクエストの修正
ここで修正するフォームリクエストは
Laravelでデータベースを操作する(クエリビルダ編)
で作成したものです
/sample/app/Http/Requests/Table2Request.php修正
‥‥
public function rules()
{
‥‥
return [
'id' => [$idRequire, 'numeric', 'max:18446744073709551615'],
'table1_id' => ['nullable', 'numeric', 'max:18446744073709551615'],
'varchar_col' => ['nullable', 'max:255'],
'int_col' => ['nullable', 'integer', 'max:2147483647'],
'datetime_col' => ['nullable', 'date_format:Y-m-d H:i:s'],
'date_col' => ['nullable', 'date_format:Y-m-d'],
'time_col' => ['nullable', 'date_format:H:i:s'],
'limit' => ['nullable', 'integer', 'min:1', 'max:200'],
'page' => ['nullable', 'integer', 'min:1', 'max:2147483647'],
'sort' => ['nullable', 'integer', 'min:0', 'max:5'],
];
}
‥‥
limit、page、sortの入力制限を追加しました
Controllerにメソッド追加
(1) /sample/app/Http/Controllers/SampleController.phpにpaginateQueryBuilderメソッドを追記
フォームリクエスト、サービスクラスのuse文は
Laravelでデータベースを操作する(クエリビルダ編)
で書きました
public function paginateQueryBuilder(Table2Request $request, Table2Service $table2Service)
{
if (is_null($request->session()->get('errors'))) {
$request->flash();
}
$id = $request->input('id');
$varchar_col = $request->input('varchar_col');
$int_col = $request->input('int_col');
$datetime_col = $request->input('datetime_col');
$date_col = $request->input('date_col');
$time_col = $request->input('time_col');
$limit = $request->input('limit') ?? 5;
$page = $request->input('page') ?? 1;
$sort = $request->input('sort') ?? 0;
$recordList = null;
if (is_null($request->session()->get('errors'))) {
$recordList = $table2Service->select($id, $varchar_col, $int_col, $datetime_col, $date_col, $time_col, $limit, $page, $sort);
}
$data = [
'recordList' => $recordList,
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => $sort
];
return view('sample.paginate', $data);
}
(2) /sample/routes/web.phpに下記を追記
Route::get('sample/paginate-query-builder', 'SampleController@paginateQueryBuilder');
viewの作成
(1) ページネーションビューの作成
ページネーションビューとは前のページへリンク、次のページへリンク、ページ番号リンクを描画するビューです
コマンドラインで
cd sample
php artisan vendor:publish --tag=laravel-pagination
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
resources/views/vendor/paginationフォルダが現れます。そのフォルダの中に5つファイルができています
その中のbootstrap-4.blade.phpが今回使うページネーションビューになります。
ページネーションビューを独自のデザインにする場合、bootstrap-4.blade.phpを修正してください
今回修正したサービスクラスで使ったpaginateメソッドの戻り値(LengthAwarePaginatorクラスのインスタンス)がデフォルトで使用するページネーションビューがpagination::bootstrap-4です。
LengthAwarePaginator::defaultViewメソッドでデフォルトビューを変えることができます。また、デフォルトビューを変えなくても、後程使うlinksメソッドの第1引数にビュー名を渡すことでもカスタムページネーションビューを使用できます
(2) /sample/resources/views/sample/paginate.blade.phpファイル作成
<html>
<head>
<title>sample</title>
<style>
.sample-table {
border-collapse:collapse;
white-space: nowrap;
border: 1px solid #000000;
}
.sample-table thead {
background-color: #33CCFF;
color: #FFFFFF;
font-weight: bold;
}
.sample-table td {
padding-left:10px;
padding-right:10px;
border: 1px solid #000000;
}
.pagination li {
display: inline-block;
}
</style>
</head>
<body>
<form action="{{ url('sample/paginate-query-builder') }}" method="get">
@error('id')
@foreach ($errors->get('id') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>id<input type="text" name="id" value="{{ old('id') }}"></div>
@error('varchar_col')
@foreach ($errors->get('varchar_col') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>varchar_col<input type="text" name="varchar_col" value="{{ old('varchar_col') }}"></div>
@error('int_col')
@foreach ($errors->get('int_col') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>int_col<input type="text" name="int_col" value="{{ old('int_col') }}"></div>
@error('datetime_col')
@foreach ($errors->get('datetime_col') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>datetime_col<input type="text" name="datetime_col" value="{{ old('datetime_col') }}"></div>
@error('date_col')
@foreach ($errors->get('date_col') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>date_col<input type="text" name="date_col" value="{{ old('date_col') }}"></div>
@error('time_col')
@foreach ($errors->get('time_col') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>time_col<input type="text" name="time_col" value="{{ old('time_col') }}"></div>
@error('limit')
@foreach ($errors->get('limit') as $error)
<div style="color:red;">{{ $error }}</div>
@endforeach
@enderror
<div>1ページ表示件数<input type="text" name="limit" value="{{ old('limit', 5) }}"></div>
<input type="submit" >
</form>
<br>
@isset($recordList)
@if ($recordList->total() === 0)
<div>
検索結果は0件です
</div>
@else
<table class="sample-table">
<thead>
<tr>
<td colspan=6>table1</td>
<td colspan=6>table2</td>
<td colspan=6>table3</td>
</tr>
<tr>
<td>id</td>
<td>varchar_col</td>
<td>int_col</td>
<td>datetime_col</td>
<td>date_col</td>
<td>time_col</td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 0])
->url($recordList->currentPage()) }}" >id</a></td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 1])
->url($recordList->currentPage()) }}" >varchar_col</a></td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 2])
->url($recordList->currentPage()) }}" >int_col</a></td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 3])
->url($recordList->currentPage()) }}" >datetime_col</a></td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 4])
->url($recordList->currentPage()) }}" >date_col</a></td>
<td><a href="{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => 5])
->url($recordList->currentPage()) }}" >time_col</a></td>
<td>id</td>
<td>varchar_col</td>
<td>int_col</td>
<td>datetime_col</td>
<td>date_col</td>
<td>time_col</td>
</tr>
</thead>
<tbody>
@foreach ($recordList as $record)
<tr>
<td>{{ $record->table1_id }}</td>
<td>{{ $record->table1_varchar_col }}</td>
<td>{{ $record->table1_int_col }}</td>
<td>{{ $record->table1_datetime_col }}</td>
<td>{{ $record->table1_date_col }}</td>
<td>{{ $record->table1_time_col }}</td>
<td>{{ $record->table2_id }}</td>
<td>{{ $record->table2_varchar_col }}</td>
<td>{{ $record->table2_int_col }}</td>
<td>{{ $record->table2_datetime_col }}</td>
<td>{{ $record->table2_date_col }}</td>
<td>{{ $record->table2_time_col }}</td>
<td>{{ $record->table3_id }}</td>
<td>{{ $record->table3_varchar_col }}</td>
<td>{{ $record->table3_int_col }}</td>
<td>{{ $record->table3_datetime_col }}</td>
<td>{{ $record->table3_date_col }}</td>
<td>{{ $record->table3_time_col }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
@endisset
@isset($recordList)
{{ $recordList->appends([
'id' => $id,
'varchar_col' => $varchar_col,
'int_col' => $int_col,
'datetime_col' => $datetime_col,
'date_col' => $date_col,
'time_col' => $time_col,
'limit' => $limit,
'page' => $page,
'sort' => $sort
])
->links() }}
@endisset
</body>
</html>
thead->tr->tdタグ内のaタグのhref属性では$recordList->appends()->url()
を実行してます。$recordList
はLengthAwarePaginatorクラスのインスタンスです。
$recordList->currentPage()
は現在のページ番号を返します
url($recordList->currentPage())は$recordList->currentPage()
ページのURLを返します。
$recordList->appends
はurl()が返すURLに付加するクエリ文字列です
よく見ていただくと、appendsメソッドに渡している配列のsort要素の値を変えています
paginate.blade.php下部に書いた$recordList->appends()->links()
は先ほど生成したbootstrap-4.blade.phpを描画します
linksメソッドの第1引数にビュー名を渡すことでbootstrap-4.blade.phpではないカスタムページネーションビューを描画することもできます
appendsメソッドは先ほど説明した通りページリンクURLに付加するクエリ文字列です
動作確認
http://localhost/laravelSample/sample/paginate-query-builder
にアクセスすると画面下部にページリンクが描画されています
POSTに対応してみる
今作成したものはGETメソッドに対応したものでした。
多くの場合、検索はGETメソッドで大丈夫でしょうが、
検索条件がたくさん設置され、長い文字列カラムが検索条件に入っている場合など、GETメソッドにした場合、URLの文字列制限に引っかかり、URLのクエリ文字列が途中で切れてしまうこともあります
そのような事情で検索をPOSTメソッドで実装した場合に対応してみましょう
(1) まず、resources/views/sample/paginate.blade.phpのformタグのmethod属性値をpostに変え、sample/routes/web.phpに定義した'sample/paginate-query-builder'のルーティングをRoute::getからRoute::match(['get', 'post']に変更します(今回は動作確認でsample/paginate-query-builderに直リンクするためgetも許容しておく)
(2) resources/views/vendor/pagination/bootstrap-4.blade.php修正
@if ($paginator->hasPages())
直下(2行目)に下記を追記
@php
$___paginatorHiddenFunc = function ($name, $value) use (&$___paginatorHiddenFunc) {
if (is_array($value)) {
foreach ($value as $k => $v) {
$___paginatorHiddenFunc($name . '[' . $k . ']', $v);
}
} else {
echo '<input type="hidden" name="' . htmlspecialchars($name, ENT_QUOTES) . '" value="' . htmlspecialchars($value, ENT_QUOTES) . '">';
}
};
$___paginatorQueryList = null;
parse_str(parse_url($paginator->url(1), PHP_URL_QUERY), $___paginatorQueryList);
@endphp
やっていることは2つです
type属性値がhiddenのinputタグを出力するメソッドを定義している($___paginatorHiddenFunc
)
1ページ目のページリンクのクエリ文字列を配列として取得している($___paginatorQueryList
)
bootstrap-4.blade.phpにはaタグが3つ書いてあります
前のページへリンク、ページ番号リンク、次のページへリンクです
これらをformタグに直します
前のページへリンク
<form action="{{ $paginator->path() }}" method="post">
@csrf
@foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
@if ($___paginatorQueryName === $paginator->getPageName())
@php $___paginatorHiddenFunc($___paginatorQueryName, $paginator->currentPage() - 1); @endphp
@elseif ($___paginatorQueryName !== '_token')
@php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
@endif
@endforeach
<input type="submit" value="‹">
</form>
ページ番号リンク
<form action="{{ $paginator->path() }}" method="post">
@csrf
@foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
@if ($___paginatorQueryName === $paginator->getPageName())
@php $___paginatorHiddenFunc($___paginatorQueryName, $page); @endphp
@elseif ($___paginatorQueryName !== '_token')
@php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
@endif
@endforeach
<input type="submit" value="{{$page}}">
</form>
次のページへリンク
<form action="{{ $paginator->path() }}" method="post">
@csrf
@foreach ($___paginatorQueryList as $___paginatorQueryName => $___paginatorQueryValue)
@if ($___paginatorQueryName === $paginator->getPageName())
@php $___paginatorHiddenFunc($___paginatorQueryName, $paginator->currentPage() + 1); @endphp
@elseif ($___paginatorQueryName !== '_token')
@php $___paginatorHiddenFunc($___paginatorQueryName, $___paginatorQueryValue); @endphp
@endif
@endforeach
<input type="submit" value="›">
</form>
やっていることは
配列として取得した1ページ目のページリンクのクエリ文字列($___paginatorQueryList
)をループし
type属性値がhiddenのinputタグを出力するメソッド($___paginatorHiddenFunc
)を実行しているだけです
(3) resources/views/sample/paginate.blade.phpファイル作成
thead->tr->tdタグ内のaタグをformタグに変更します
<form action="{{ $recordList->path() }}" method="post">
@csrf
<input type="hidden" name="id" value="{{ $id }}">
<input type="hidden" name="varchar_col" value="{{ $varchar_col }}">
<input type="hidden" name="int_col" value="{{ $int_col }}">
<input type="hidden" name="datetime_col" value="{{ $datetime_col }}">
<input type="hidden" name="date_col" value="{{ $date_col }}">
<input type="hidden" name="time_col" value="{{ $time_col }}">
<input type="hidden" name="limit" value="{{ $limit }}">
<input type="hidden" name="sort" value="0">{{-- ここは各カラムの番号にする --}}
<input type="hidden" name="page" value="{{ $page }}">
<input type="submit" value="id">{{-- ここは各カラム名にする --}}
</form>
動作確認
http://localhost/laravelSample/sample/paginate-query-builder
にアクセスします
ページリンクをクリックするとPOST送信できました
見た目は自由にCSSで変えてください