Laravel 專案 偽靜態分頁處理

朕略顯ぼうっと萌發表於2019-12-31

手上有個 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);
本作品採用《CC 協議》,轉載必須註明作者和本文連結

kingofzihua

相關文章