首頁  >  文章  >  php框架  >  laravel實作隨著Resource返回自訂分頁資訊!

laravel實作隨著Resource返回自訂分頁資訊!

藏色散人
藏色散人轉載
2022-02-03 04:00:303071瀏覽

laravel實作隨著Resource返回自訂分頁資訊!


最近向Laravel 框架提交了一個想法— 在PaginatedResourceResponse 中加入一個自訂分頁資訊方法的偵測,以便在使用Resource 類別輸出資訊時,能夠非常方便地自訂分頁資訊。

為什麼需要它

我基本上都是在開發 API。早期時候我都是直接返回,但是這種方式有時候會出現一些問題,也不方便維護,加上經常需要添加自定義字段和針對不同端給出不同數據的情況,我後來就一直在使用Resource 來定義傳回的資料。 【推薦:laravel影片教學

使用 Resource 很方便也能讓邏輯清晰。但它有個不好的地方,那就是分頁資訊太多了。針對API 專案而言,大多數情況下,預設輸出的分頁資訊裡很多欄位並不需要,並且由於經常對接的是一些老項目,需要沿用老的資料格式或做相容,分頁資訊的欄位大不相同,沒辦法直接使用預設回傳的分頁資訊。

我不知道大家是怎麼處理類似情況時的分頁資訊的,但在此之前,為了能夠達到目的,我通常有兩種做法,一是自訂Response,在這裡面把資料資訊重新定義,二是將Resource 相關的類別全部自訂一遍。

我對Laravel 底層並不是很了解,我也不擅長做抽象的框架開發,但是在經歷這些之後,我發現事情能夠變得簡單很多,正如我在PR 闡述的那樣,如果可以在src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php 中組成分頁資訊時,能夠使用其對應Resource 類別的元件分頁訊息,那不就不需要每次大費周章的進行自訂很多類了嗎。於是我就提交了這個想法給 Laravel 框架。這個提交在一開始並沒有被直接接受,而是在經過 Taylor 調整後被合併,並發佈在 v8.73.2。

這是我第一次向 Laravel 貢獻程式碼,也是第一次向這麼大的程式碼庫提交合併請求,雖然沒有被直接採用,但結果足以振奮人心。

使用範例

那麼,我來簡單的範例一下如何使用吧。

預設輸出

{  
    "data": [],
    "links": {
        "first": "http://cooman.cootab-v4.test/api/favicons?page=1",
        "last": "http://cooman.cootab-v4.test/api/favicons?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« 上一页",
                "active": false
            },
            {
                "url": "http://cooman.cootab-v4.test/api/favicons?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "下一页 »",
                "active": false
            }
        ],
        "path": "http://cooman.cootab-v4.test/api/favicons",
        "per_page": 15,
        "to": 5,
        "total": 5
    }}

這是 Laravel 預設輸出的分頁信息,是不是很多字段,當然這足夠應對很多場景的使用。但有時候也會因此而犯錯。我們需要一點靈活。

使用 ResourceCollection 類別時

我們先來看看底層邏輯吧!

當在控制器傳回一個 ResourceCollection 時,最終會呼叫其 toResponse 方法以回應。那麼可以直接找到該方法看看:

   /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function toResponse($request)
    {
        if ($this->resource instanceof AbstractPaginator || $this->resource instanceof AbstractCursorPaginator) {
            return $this->preparePaginatedResponse($request);
        }

        return parent::toResponse($request);
    }

看到沒,如果當前資源是個分頁物件時,它就把任務轉向處理分頁回應了。接著看:

    /**
     * Create a paginate-aware HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    protected function preparePaginatedResponse($request)
    {
        if ($this->preserveAllQueryParameters) {
            $this->resource->appends($request->query());
        } elseif (! is_null($this->queryParameters)) {
            $this->resource->appends($this->queryParameters);
        }

        return (new PaginatedResourceResponse($this))->toResponse($request);
    }

噢,它又轉給了PaginatedResourceResponse ,這是我們最終需要修改的類,由於toResponse 的內容太長,就不在這裡貼出,反正就是在這裡開始組成回應的數據,分頁資訊當然也是在這裡面做的處理,不過它有個獨立的方法。這個方法就是paginationInformation, 這是在提交PR 之前的邏輯:

/**
     * Add the pagination information to the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function paginationInformation($request)
    {
        $paginated = $this->resource->resource->toArray();

        return [
            'links' => $this->paginationLinks($paginated),
            'meta' => $this->meta($paginated),
        ];
    }

如果你細心的話,你應該能夠想到,這裡的$this->resource 其實就是上面的ResourceCollection 的實例,那麼它的resource 就是我們的清單數據,也就是分頁資訊實例。既然如此,那我們為何不能在 ResourceCollection 中進行分頁資訊的處理呢?當然可以,但我們需要加點東西,這就是我提交的想法。

合併PR 之後,它的邏輯是這樣的:

/**
     * Add the pagination information to the response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function paginationInformation($request)
    {
        $paginated = $this->resource->resource->toArray();

        $default = [
            'links' => $this->paginationLinks($paginated),
            'meta' => $this->meta($paginated),
        ];

        if (method_exists($this->resource, 'paginationInformation')) {
            return $this->resource->paginationInformation($request, $paginated, $default);
        }

        return $default;
    }

很簡單的處理方式,如果對應資源類別中有自訂的分頁資訊組成方法,那就使用它自己的,目前而言,這確實是個好想法。

於此,如何自訂分頁資訊應該很清晰了。那就是在自己對應的 ResourceCollection 類別中加入 paginationInformation 方法即可,例如:

public function paginationInformation($request, $paginated, $default): array
    {
        return [
            'page' => $paginated['current_page'],
            'per_page' => $paginated['per_page'],
            'total' => $paginated['total'],
            'total_page' => $paginated['last_page'],
        ];
    }

这是自定义后的数据输出情况:

{
    "data": [],
    "page": 1,
    "per_page": 15,
    "total": 5,
    "total_page": 1}

结果如我所愿。

使用 Resource 类时

我通常只喜欢定义一个 Resource 类来应对单个对象和列表的情况,这里主要关注如何处理列表数据的分页自定义。

在控制器中,我一般都是这样使用:

public function Index(){
    // ....
    return  SomeResource::collection($paginatedData);}

再来看看 collection 方法里做了什么:

   /**
     * Create a new anonymous resource collection.
     *
     * @param  mixed  $resource
     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
     */
    public static function collection($resource)
    {
        return tap(new AnonymousResourceCollection($resource, static::class), function ($collection) {
            if (property_exists(static::class, 'preserveKeys')) {
                $collection->preserveKeys = (new static([]))->preserveKeys === true;
            }
        });
    }

原来它把数据转给了 ResourceCollection,那么只需要将这个  AnonymousResourceCollection 做个自定义不就可以了。

总结

这是一个很小优化,但是很有用。

在此之前,如果想要随着 Resource 返回自定义分页信息,会比较麻烦,需要自定义很多东西,这样的方式,对老用户而言小菜一碟,但是对新手就可能是件棘手的问题。那么自此之后,无论是老用户还是新手这件事将变得易如反掌。只需要在对应的 ResourceCollection 类中添加 paginationInformation 方法,类似下面这样:

public function paginationInformation($request, $paginated, $default): array
    {
        return [
            'page' => $paginated['current_page'],
            'per_page' => $paginated['per_page'],
            'total' => $paginated['total'],
            'total_page' => $paginated['last_page'],
        ];
    }

不过,如果你使用的是 Resource::collection($pageData) 方式,那么还需要额外自定义一个 ResourceCollection 类,并重写对应 Resource 类的 collection 方法。

我通常会定义一个对应的基类,然后其它的都继承它。也可以做个 trait,然后共用。

最后

其实,这个想法我很早就想提交的,但是我一直比较犹豫,这到底是不是一个很大众的需求。不过我最后想明白了,这样做既然能为我节省大量重复且危险的工作,有那么多的开发者,总会有人需要的,所以我提交了,同时也是验证下我的想法到底是否可行,我的做法是否最优,结果当然是我学到了很多,比如写稍微复杂的测试用例。

另外,我想知道大家有没其它方法,或你们是怎么对待不同情况的分页信息的。

最后的最后,你如果也有好的想法,那么尽快提交吧!                                       

以上是laravel實作隨著Resource返回自訂分頁資訊!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除