跳轉至內容

資料庫:分頁

簡介

在其他框架中,分頁可能非常痛苦。我們希望 Laravel 的分頁方式能讓人耳目一新。Laravel 的分頁器與查詢構建器 Eloquent ORM 整合,無需任何配置即可方便、易用地對資料庫記錄進行分頁。

預設情況下,分頁器生成的 HTML 與 Tailwind CSS 框架相容;不過,也支援 Bootstrap 分頁。

Tailwind

如果您在使用 Tailwind 4.x 的同時使用 Laravel 的預設 Tailwind 分頁檢視,應用程式的 resources/css/app.css 檔案將已經正確配置,以便 @source 引用 Laravel 的分頁檢視。

1@import 'tailwindcss';
2 
3@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';

基本用法

分頁查詢構建器結果

有幾種方法可以對條目進行分頁。最簡單的方法是在查詢構建器Eloquent 查詢上使用 paginate 方法。paginate 方法會自動根據使用者當前檢視的頁面來設定查詢的 “limit” 和 “offset”。預設情況下,當前頁面是透過 HTTP 請求中的 page 查詢字串引數的值來檢測的。Laravel 會自動檢測該值,並將其自動插入到分頁器生成的連結中。

在本例中,傳遞給 paginate 方法的唯一引數是您希望“每頁”顯示的條目數。在此例中,我們指定希望每頁顯示 15 條記錄。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Support\Facades\DB;
6use Illuminate\View\View;
7 
8class UserController extends Controller
9{
10 /**
11 * Show all application users.
12 */
13 public function index(): View
14 {
15 return view('user.index', [
16 'users' => DB::table('users')->paginate(15)
17 ]);
18 }
19}

簡單分頁

paginate 方法在從資料庫檢索記錄之前,會先計算查詢匹配的記錄總數。這樣做是為了讓分頁器知道總共有多少頁記錄。但是,如果您不打算在應用程式的 UI 中顯示總頁數,那麼記錄計數查詢就是不必要的。

因此,如果您只需要在應用程式 UI 中顯示簡單的“下一頁”和“上一頁”連結,可以使用 simplePaginate 方法來執行單次、高效的查詢。

1$users = DB::table('users')->simplePaginate(15);

分頁 Eloquent 結果

您也可以對 Eloquent 查詢進行分頁。在此例中,我們將對 App\Models\User 模型進行分頁,並指定每頁顯示 15 條記錄。正如您所見,其語法與查詢構建器的分頁結果幾乎完全相同。

1use App\Models\User;
2 
3$users = User::paginate(15);

當然,您也可以在設定查詢的其他約束(例如 where 子句)之後呼叫 paginate 方法。

1$users = User::where('votes', '>', 100)->paginate(15);

您也可以在對 Eloquent 模型進行分頁時使用 simplePaginate 方法。

1$users = User::where('votes', '>', 100)->simplePaginate(15);

同樣,您可以使用 cursorPaginate 方法對 Eloquent 模型進行遊標分頁。

1$users = User::where('votes', '>', 100)->cursorPaginate(15);

每頁多個分頁器例項

有時,您可能需要在應用程式渲染的單個螢幕上渲染兩個獨立的分頁器。但是,如果兩個分頁器例項都使用 page 查詢字串引數來儲存當前頁面,則會導致衝突。為了解決此衝突,您可以透過 paginatesimplePaginatecursorPaginate 方法的第三個引數,傳遞您希望用於儲存分頁器當前頁面的查詢字串引數名稱。

1use App\Models\User;
2 
3$users = User::where('votes', '>', 100)->paginate(
4 $perPage = 15, $columns = ['*'], $pageName = 'users'
5);

遊標分頁

雖然 paginatesimplePaginate 使用 SQL “offset” 子句建立查詢,但遊標分頁的工作原理是構建“where”子句來比較查詢中包含的排序欄位值,這在所有 Laravel 分頁方法中提供了最高效的資料庫效能。這種分頁方法特別適用於大資料集和“無限滾動”使用者介面。

與基於 offset 的分頁不同(它在分頁器生成的 URL 查詢字串中包含頁碼),基於遊標的分頁會在查詢字串中放置一個“遊標”字串。遊標是一個編碼字串,包含下一次分頁查詢應開始的位置以及應分頁的方向。

1https:///users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

您可以透過查詢構建器提供的 cursorPaginate 方法建立一個基於遊標的分頁器例項。該方法返回 Illuminate\Pagination\CursorPaginator 的例項。

1$users = DB::table('users')->orderBy('id')->cursorPaginate(15);

一旦獲得了遊標分頁器例項,您就可以像通常使用 paginatesimplePaginate 方法一樣顯示分頁結果。有關遊標分頁器提供的例項方法的更多資訊,請查閱遊標分頁器例項方法文件

您的查詢必須包含 “order by” 子句才能利用遊標分頁。此外,查詢排序所依據的列必須屬於您正在分頁的表。

遊標分頁與 Offset 分頁的對比

為了說明 offset 分頁和遊標分頁之間的差異,讓我們檢查一些 SQL 查詢示例。以下兩個查詢都將顯示按 id 排序的 users 表的“第二頁”結果。

1# Offset Pagination...
2select * from users order by id asc limit 15 offset 15;
3 
4# Cursor Pagination...
5select * from users where id > 15 order by id asc limit 15;

與 offset 分頁相比,遊標分頁查詢具有以下優勢:

  • 對於大資料集,如果 “order by” 列被索引,遊標分頁將提供更好的效能。這是因為 “offset” 子句需要掃描所有之前匹配的資料。
  • 對於頻繁寫入的資料集,如果結果剛被新增或從使用者當前檢視的頁面中刪除,offset 分頁可能會跳過記錄或顯示重複記錄。

但是,遊標分頁有以下限制:

  • simplePaginate 一樣,遊標分頁只能用於顯示“下一頁”和“上一頁”連結,不支援生成帶有頁碼的連結。
  • 它要求排序基於至少一個唯一列或唯一列的組合。不支援包含 null 值的列。
  • 只有在“order by”子句中的查詢表示式被別名化並同時新增到“select”子句中時,才支援這些表示式。
  • 不支援帶有引數的查詢表示式。

手動建立分頁器

有時,您可能希望手動建立一個分頁例項,並將記憶體中已有的陣列項傳遞給它。您可以根據需要建立一個 Illuminate\Pagination\PaginatorIlluminate\Pagination\LengthAwarePaginatorIlluminate\Pagination\CursorPaginator 例項。

PaginatorCursorPaginator 類不需要知道結果集中的總項數;因此,這些類沒有用於檢索最後一頁索引的方法。LengthAwarePaginator 接受的引數與 Paginator 幾乎相同;但它需要結果集中總項數的計數。

換句話說,Paginator 對應於查詢構建器上的 simplePaginate 方法,CursorPaginator 對應於 cursorPaginate 方法,而 LengthAwarePaginator 對應於 paginate 方法。

手動建立分頁器例項時,您應該手動“切片”傳遞給分頁器的結果陣列。如果您不確定如何執行此操作,請檢視 PHP 的 array_slice 函式。

自定義分頁 URL

預設情況下,分頁器生成的連結將匹配當前請求的 URI。但是,分頁器的 withPath 方法允許您自定義生成連結時分頁器使用的 URI。例如,如果您希望分頁器生成類似於 http://example.com/admin/users?page=N 的連結,則應將 /admin/users 傳遞給 withPath 方法。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 $users = User::paginate(15);
5 
6 $users->withPath('/admin/users');
7 
8 // ...
9});

追加查詢字串值

您可以使用 appends 方法追加到分頁連結的查詢字串中。例如,要將 sort=votes 追加到每個分頁連結,您應該進行以下 appends 呼叫:

1use App\Models\User;
2 
3Route::get('/users', function () {
4 $users = User::paginate(15);
5 
6 $users->appends(['sort' => 'votes']);
7 
8 // ...
9});

如果您希望將當前請求的所有查詢字串值追加到分頁連結,可以使用 withQueryString 方法。

1$users = User::paginate(15)->withQueryString();

追加雜湊片段

如果您需要將“雜湊片段”追加到分頁器生成的 URL 中,可以使用 fragment 方法。例如,要將 #users 追加到每個分頁連結的末尾,您應該這樣呼叫 fragment 方法:

1$users = User::paginate(15)->fragment('users');

顯示分頁結果

呼叫 paginate 方法時,您將獲得 Illuminate\Pagination\LengthAwarePaginator 的例項,而呼叫 simplePaginate 方法會返回 Illuminate\Pagination\Paginator 的例項。最後,呼叫 cursorPaginate 方法會返回 Illuminate\Pagination\CursorPaginator 的例項。

這些物件提供了幾種描述結果集的方法。除了這些輔助方法之外,分頁器例項還是迭代器,可以像陣列一樣迴圈。因此,一旦檢索到結果,您就可以使用 Blade 顯示結果並渲染頁面連結。

1<div class="container">
2 @foreach ($users as $user)
3 {{ $user->name }}
4 @endforeach
5</div>
6 
7{{ $users->links() }}

links 方法將渲染結果集中其餘頁面的連結。這些連結中的每一個都將已經包含正確的 page 查詢字串變數。請記住,links 方法生成的 HTML 與 Tailwind CSS 框架相容。

當分頁器顯示分頁連結時,會顯示當前頁碼以及當前頁面前後三頁的連結。使用 onEachSide 方法,您可以控制分頁器生成的中間滑動連結視窗中,當前頁面每一側顯示多少個附加連結。

1{{ $users->onEachSide(5)->links() }}

將結果轉換為 JSON

Laravel 分頁器類實現了 Illuminate\Contracts\Support\Jsonable 契約介面並公開了 toJson 方法,因此將分頁結果轉換為 JSON 非常容易。您也可以透過從路由或控制器動作中返回分頁器例項,將其轉換為 JSON。

1use App\Models\User;
2 
3Route::get('/users', function () {
4 return User::paginate();
5});

來自分頁器的 JSON 將包含諸如 totalcurrent_pagelast_page 等元資訊。結果記錄可透過 JSON 陣列中的 data 鍵獲得。以下是從路由返回分頁器例項所建立的 JSON 示例:

1{
2 "total": 50,
3 "per_page": 15,
4 "current_page": 1,
5 "last_page": 4,
6 "current_page_url": "http://laravel.app?page=1",
7 "first_page_url": "http://laravel.app?page=1",
8 "last_page_url": "http://laravel.app?page=4",
9 "next_page_url": "http://laravel.app?page=2",
10 "prev_page_url": null,
11 "path": "http://laravel.app",
12 "from": 1,
13 "to": 15,
14 "data":[
15 {
16 // Record...
17 },
18 {
19 // Record...
20 }
21 ]
22}

自定義分頁檢視

預設情況下,用於顯示分頁連結的檢視與 Tailwind CSS 框架相容。但是,如果您不使用 Tailwind,可以自由定義自己的檢視來渲染這些連結。在分頁器例項上呼叫 links 方法時,您可以將檢視名稱作為第一個引數傳遞給該方法。

1{{ $paginator->links('view.name') }}
2 
3<!-- Passing additional data to the view... -->
4{{ $paginator->links('view.name', ['foo' => 'bar']) }}

不過,自定義分頁檢視最簡單的方法是使用 vendor:publish 命令將其匯出到您的 resources/views/vendor 目錄。

1php artisan vendor:publish --tag=laravel-pagination

此命令將把檢視放入應用程式的 resources/views/vendor/pagination 目錄中。該目錄中的 tailwind.blade.php 檔案對應於預設的分頁檢視。您可以編輯此檔案以修改分頁 HTML。

如果您想指定另一個檔案作為預設分頁檢視,可以在 App\Providers\AppServiceProvider 類的 boot 方法中呼叫分頁器的 defaultViewdefaultSimpleView 方法。

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Pagination\Paginator;
6use Illuminate\Support\ServiceProvider;
7 
8class AppServiceProvider extends ServiceProvider
9{
10 /**
11 * Bootstrap any application services.
12 */
13 public function boot(): void
14 {
15 Paginator::defaultView('view-name');
16 
17 Paginator::defaultSimpleView('view-name');
18 }
19}

使用 Bootstrap

Laravel 包含了使用 Bootstrap CSS 構建的分頁檢視。要使用這些檢視而不是預設的 Tailwind 檢視,您可以在 App\Providers\AppServiceProvider 類的 boot 方法中呼叫分頁器的 useBootstrapFouruseBootstrapFive 方法。

1use Illuminate\Pagination\Paginator;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Paginator::useBootstrapFive();
9 Paginator::useBootstrapFour();
10}

Paginator / LengthAwarePaginator 例項方法

每個分頁器例項透過以下方法提供額外的資訊:

方法 描述
$paginator->count() 獲取當前頁面的條目數。
$paginator->currentPage() 獲取當前頁碼。
$paginator->firstItem() 獲取結果中第一項的結果編號。
$paginator->getOptions() 獲取分頁器選項。
$paginator->getUrlRange($start, $end) 建立一系列分頁 URL。
$paginator->hasPages() 確定是否有足夠的條目可以拆分為多個頁面。
$paginator->hasMorePages() 確定資料儲存中是否有更多條目。
$paginator->items() 獲取當前頁面的條目。
$paginator->lastItem() 獲取結果中最後一項的結果編號。
$paginator->lastPage() 獲取最後一頁的頁碼。(使用 simplePaginate 時不可用)。
$paginator->nextPageUrl() 獲取下一頁的 URL。
$paginator->onFirstPage() 確定分頁器是否在第一頁。
$paginator->onLastPage() 確定分頁器是否在最後一頁。
$paginator->perPage() 每頁顯示的條目數。
$paginator->previousPageUrl() 獲取上一頁的 URL。
$paginator->total() 確定資料儲存中匹配條目的總數。(使用 simplePaginate 時不可用)。
$paginator->url($page) 獲取指定頁碼的 URL。
$paginator->getPageName() 獲取用於儲存頁碼的查詢字串變數。
$paginator->setPageName($name) 設定用於儲存頁碼的查詢字串變數。
$paginator->through($callback) 使用回撥函式轉換每一項。

遊標分頁器例項方法

每個遊標分頁器例項透過以下方法提供額外資訊:

方法 描述
$paginator->count() 獲取當前頁面的條目數。
$paginator->cursor() 獲取當前遊標例項。
$paginator->getOptions() 獲取分頁器選項。
$paginator->hasPages() 確定是否有足夠的條目可以拆分為多個頁面。
$paginator->hasMorePages() 確定資料儲存中是否有更多條目。
$paginator->getCursorName() 獲取用於儲存遊標的查詢字串變數。
$paginator->items() 獲取當前頁面的條目。
$paginator->nextCursor() 獲取下一組條目的遊標例項。
$paginator->nextPageUrl() 獲取下一頁的 URL。
$paginator->onFirstPage() 確定分頁器是否在第一頁。
$paginator->onLastPage() 確定分頁器是否在最後一頁。
$paginator->perPage() 每頁顯示的條目數。
$paginator->previousCursor() 獲取上一組條目的遊標例項。
$paginator->previousPageUrl() 獲取上一頁的 URL。
$paginator->setCursorName() 設定用於儲存遊標的查詢字串變數。
$paginator->url($cursor) 獲取給定遊標例項的 URL。