我們知道,Laravel 自帶的分頁器方法包含 simplePaginate
和 paginate
方法,一個返回不帶頁碼的分頁鏈接,另一個返回帶頁碼的分頁鏈接,但是這兩種分頁鏈接頁碼都是以帶問號的動態參數形式附加在查詢字符串中,形如 https://laravelacademy.org?page=100
,但是這種包含動態參數的 URL 格式對 SEO 不友好,我們最好將其轉化為 https://laravelacademy.org/page/100
這種不帶問號的偽靜態分頁鏈接格式,遺憾的是 Laravel 並沒有提供對這種偽靜態分頁鏈接格式的自定義支持,只好自己動手了。
我們將基於 paginate
方法實現的分頁進行擴展,只是修改其分頁鏈接格式,該方法底層調用了 Illuminate\Pagination\LengthAwarePaginator
類實現分頁,所以我們需要自定義一個繼承自該分頁器類的 app/Utils/AcademyPaginator.php。
一、創建文件app/Utils/AcademyPaginator.php
<?php namespace App\Utils; use Illuminate\Container\Container; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Str; use Illuminate\Pagination\Paginator; use Illuminate\Pagination\LengthAwarePaginator as BasePaginator; class AcademyPaginator extends BasePaginator { /** * 重寫頁面 URL 實現代碼,去掉分頁中的問號,實現偽靜態鏈接 * @param int $page * @return string */ public function url($page) { if ($page <= 0) { $page = 1; } // 移除路徑尾部的/ $path = rtrim($this->path, '/'); // 如果路徑中包含分頁信息則正則替換頁碼,否則將頁碼信息追加到路徑末尾 if (preg_match('/\/page\/\d+/', $path)) { $path = preg_replace('/\/page\/\d+/', '/page/' . $page, $path); } else { $path .= '/page/' . $page; } $this->path = $path; if ($this->query) { $url = $this->path . (Str::contains($this->path, '?') ? '&' : '?') . http_build_query($this->query, '', '&') . $this->buildFragment(); } elseif ($this->fragment) { $url = $this->path . $this->buildFragment(); } else { $url = $this->path; } return $url; } /** * 重寫當前頁設置方法 * * @param int $currentPage * @param string $pageName * @return int */ protected function setCurrentPage($currentPage, $pageName) { if (!$currentPage && preg_match('/\/page\/(\d+)/', $this->path, $matches)) { $currentPage = $matches[1]; } return $this->isValidPageNumber($currentPage) ? (int) $currentPage : 1; } /** * 將新增的分頁方法注冊到查詢構建器中,以便在模型實例上使用 * 注冊方式: * 在 AppServiceProvider 的 boot 方法中注冊:AcademyPaginator::rejectIntoBuilder(); * 使用方式: * 將之前代碼中在模型實例上調用 paginate 方法改為調用 seoPaginate 方法即可: * Article::where('status', 1)->seoPaginate(15, ['*'], 'page', page); */ public static function injectIntoBuilder() { /* * $perPage 每頁顯示多少條 * $columns 查詢的字段 * $pageName 翻頁鏈接的參數名 * $page 當前頁數 * */ Builder::macro('seoPaginate', function ($perPage, $columns, $pageName, $page) { $perPage = $perPage ?: $this->model->getPerPage(); $items = ($total = $this->toBase()->getCountForPagination()) ? $this->forPage($page, $perPage)->get($columns) : $this->model->newCollection(); $options = [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]; return Container::getInstance()->makeWith(AcademyPaginator::class, compact( 'items', 'total', 'perPage', 'page', 'options' )); }); } }
二、在 AppServiceProvider
的 boot
方法中全局調用這個注入:
// 為查詢構建器注入自己實現的分頁器方法 AcademyPaginator::injectIntoBuilder();
這樣,在模型實例上調用 seoPaginate
方法,將通過 AcademyPaginator
進行分頁。接下來,就可以在自己的代碼中編寫以下這種代碼實現偽靜態分頁鏈接了:
$articles = Article::with('author', 'category')->public()->orderBy('id', 'desc')->seoPaginate($pageSize, $this->listColumns, 'page', $page);