手上有個 Laravel 的項目,要求做偽靜態處理,項目中使用了 Laravel 自帶的分頁組件,分頁組件分頁會在你的 URL 用 Query 的方式做頁碼的傳遞,達不到偽靜態的要求。
想要的效果
我們偽靜態想要的效果大體是這樣的:
/software/3dmax/created_at/page-1.html
對應 Laravel 的路由是:
/software/{category}/{order}/page-{page}.html因為 Laravel 路由本身是支持路由參數的,所以說 我們變量的獲取是完全沒有問題的,但是 Laravel 自帶的分頁組件會將你的 參數 用 Query 的方式做傳遞,所以生成的分頁地址是下面這種
/software/3dmax/created_at/page-1.html?category=3dmax&order=created_at&page=2
這不是我們需要的,所以我們需要對 Laravel 自帶的分頁組件進行修改。
Laravel 分頁組件
在 Laravel 中我們如果需要分頁,會調用 模型中的 paginate 方法,然后傳遞每頁的頁碼。
paginate 方法會調用 Illuminate\Database\Concerns\BuildsQueries 下的paginator方法。
paginator 方法會構造一個 Illuminate\Pagination\LengthAwarePaginator的實例。
Illuminate\Pagination\LengthAwarePaginator 會使用 Illuminate\Pagination\AbstractPaginator 中的url方法進行構造請求參數和url。
現在我們找到生成 URL 的地方了,我們需要做的就是在這里修改。
重寫分頁組件
Laravel 中本身支持自定義分頁組件,But 我們做的不是自定義分頁,我們需要對于方法進行重寫。
創建 LengthAwarePaginator 類
mkdir app/Pagination touch app/Pagination/LengthAwarePaginator.php
文件 app/Pagination/LengthAwarePaginator.php 內容:
<?php
namespace App\Pagination;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Pagination\LengthAwarePaginator as BasePaginator;
class LengthAwarePaginator extends BasePaginator
{
}重寫 Url 方法
首先 Laravel 自帶的分頁 會把路由里面的參數放到 Query中,我們需要的是 參數還是放到地址中。
獲取到所有的 query 參數
判斷需要分頁的頁面路由中是否有綁定的路由參數
如果沒有的話,我們就走 Laravel 本身的分頁
如果有的話,我們就通過路由和路由參數進行構建地址,并把它從 query 參數中剔除
判斷下當前的 query 參數中是否還有參數,如果還有的話,我們就和之前一樣。
修改 app/Pagination/LengthAwarePaginator.php下內容:
...
public function url($page)
{
if ($page <= 0) {
$page = 1;
}
$parameters = [$this->pageName => $page];
if (count($this->query) > 0) {
$parameters = array_merge($this->query, $parameters);
}
//判斷的參數是否在 路由中 需要綁定的數據
$params = \request()->route()->parameters();
if (!empty($params)) {
foreach ($parameters as $key => $parameter) {
if (isset($params[$key])) {
$params[$key] = $parameter;
unset($parameters[$key]);
}
}
$path = route(\request()->route()->getAction('as'), $params);
} else {
$path = $this->path;
}
// 判斷是否有參數
if (empty(Arr::query($parameters))) {
return $path . $this->buildFragment();
}
return $path
. (Str::contains($this->path, '?') ? '&' : '?')
. Arr::query($parameters)
. $this->buildFragment();
}
...使用自定義的分頁組件
在 Laravel 中我們如果需要分頁,會調用 模型中的 paginate 方法,但是paginate方法的定義在Illuminate\Database\Eloquent\Builder下,如果我們需要重寫的話,會很麻煩,并且還有一個問題就是,并不是我們所有的分頁都是需要偽靜態的,比如我們用戶中心的數據可能不太需要偽靜態。所以我們需要一個可以手動設置的東西,Larave 模型中有一個 本地作用域,我們可以寫一個方法staticPaginate,當需要使用靜態分頁的時候,我們可以Model->query()->staticPaginate(); 來調用,所需要的參數和 Laravel 自帶的 pageinage 方法類似。
公共的Model 基類文件
Laravel項目中的 Model 我們一般不會直接繼承Illuminate\Database\Eloquent\Model 我們一般都在 app\Models 目錄定義一個 Model 基類,所有的模型都繼承自 Model 基類,這并不是必須的,只是這樣的話對于模型修改,或添加公共的方法比較方便。
在模型中定義本地作用域
你只需要拷貝 Illuminate\Database\Eloquent\Builder下的paginate方法的內容并修改$this的指向就可以了
...
use Illuminate\Pagination\Paginator;
# Laravel 自帶的。
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
...
/**
* 自定義靜態分頁
* @author kingofzihua
* @param Builder $builder
* @param int $perPage
* @param array $columns
* @param string $pageName
* @param int|null $page
* @return LengthAwarePaginator
*
* @throws \InvalidArgumentException
*/
public function scopeStaticPaginate($builder, $perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
if (request('page')) {
request()->offsetSet('page', request('page'));
}
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$perPage = $perPage ?: $builder->getModel()->getPerPage();
$results = ($total = $builder->toBase()->getCountForPagination())
? $builder->forPage($page, $perPage)->get($columns)
: $builder->getModel()->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
...替換自定義的分頁組件
# 替換
use App\Pagination\LengthAwarePaginator;
# --- use Illuminate\Contracts\Pagination\LengthAwarePaginator; // 注釋
...
/**
*
* @param \Illuminate\Support\Collection $items
* @param int $total
* @param int $perPage
* @param int $currentPage
* @param array $options
* @return LengthAwarePaginator
*/
protected function paginator($items, $total, $perPage, $currentPage, $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, compact(
'items', 'total', 'perPage', 'currentPage', 'options'
));
}
...在項目中使用靜態分頁組件
Model::query()->staticPaginate($pageSize);






