0

0

laravel实现随着Resource返回自定义分页信息!

藏色散人

藏色散人

发布时间:2022-02-03 04:00:30

|

3291人浏览过

|

来源于learnku

转载

e1470ab37e57f79e84a6826b98c6595.png


最近向 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;
    }

很简单的处理方式,如果对应资源类中有自定义的分页信息组建方法,那就使用它自己的,目前而言,这确实是个好想法。

Favird No-Code Tools
Favird No-Code Tools

无代码工具的聚合器

下载

于此,如何自定义分页信息应该很清晰了。那就是在自己相应的 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,然后共用。

最后

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

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

38

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

83

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

97

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

223

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

458

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

169

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

246

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

34

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Laravel---API接口
Laravel---API接口

共7课时 | 0.7万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

PHP面向对象基础课程(更新中)
PHP面向对象基础课程(更新中)

共12课时 | 0.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号