資料庫:分頁
簡介
在其他框架中,分頁可能非常痛苦。我們希望 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(): View14 {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 查詢字串引數來儲存當前頁面,則會導致衝突。為了解決此衝突,您可以透過 paginate、simplePaginate 和 cursorPaginate 方法的第三個引數,傳遞您希望用於儲存分頁器當前頁面的查詢字串引數名稱。
1use App\Models\User;2 3$users = User::where('votes', '>', 100)->paginate(4 $perPage = 15, $columns = ['*'], $pageName = 'users'5);
遊標分頁
雖然 paginate 和 simplePaginate 使用 SQL “offset” 子句建立查詢,但遊標分頁的工作原理是構建“where”子句來比較查詢中包含的排序欄位值,這在所有 Laravel 分頁方法中提供了最高效的資料庫效能。這種分頁方法特別適用於大資料集和“無限滾動”使用者介面。
與基於 offset 的分頁不同(它在分頁器生成的 URL 查詢字串中包含頁碼),基於遊標的分頁會在查詢字串中放置一個“遊標”字串。遊標是一個編碼字串,包含下一次分頁查詢應開始的位置以及應分頁的方向。
1https:///users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
您可以透過查詢構建器提供的 cursorPaginate 方法建立一個基於遊標的分頁器例項。該方法返回 Illuminate\Pagination\CursorPaginator 的例項。
1$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
一旦獲得了遊標分頁器例項,您就可以像通常使用 paginate 和 simplePaginate 方法一樣顯示分頁結果。有關遊標分頁器提供的例項方法的更多資訊,請查閱遊標分頁器例項方法文件。
您的查詢必須包含 “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\Paginator、Illuminate\Pagination\LengthAwarePaginator 或 Illuminate\Pagination\CursorPaginator 例項。
Paginator 和 CursorPaginator 類不需要知道結果集中的總項數;因此,這些類沒有用於檢索最後一頁索引的方法。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 @endforeach5</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 將包含諸如 total、current_page、last_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 方法中呼叫分頁器的 defaultView 和 defaultSimpleView 方法。
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(): void14 {15 Paginator::defaultView('view-name');16 17 Paginator::defaultSimpleView('view-name');18 }19}
使用 Bootstrap
Laravel 包含了使用 Bootstrap CSS 構建的分頁檢視。要使用這些檢視而不是預設的 Tailwind 檢視,您可以在 App\Providers\AppServiceProvider 類的 boot 方法中呼叫分頁器的 useBootstrapFour 或 useBootstrapFive 方法。
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。 |