Laravel 的限制条数再分页

本文搬运自我自己的博客

1. 需求

        有这么一个需求,我想取出一个表(比如user表)中,按照某一排序规则(比如按照时间倒叙),取出前100条,进行分页,每页10条。应该如何实现?自然而然可能会这样写下:


$users = User::orderBy('id','desc')->limit(100)->paginate(10)

最后打印结果可以发现,limit并未生效,依旧是将所有结果进行分页。

2. 准备

        本文中用到的user模型,数据,控制器,路由之类的都已经在另一篇文章 手摸手教你让Laravel开发Api更得心应手 创建好了。

users表中的数据

user-data

3. 限制条数再分页

继续通过paginate方法来分页以及行不通了。确幸Laravel框架给我们提供了自定义分页类,我们通过使用自定义分页类来达到我们限制条数再分页的需求。

下面我们分别讲解数组手动分页以及模型对象手动分页

3.1. 需求

通过id来倒叙排序,并且取出前6条来分页,每页2条数据

3.2. 数组

3.2.1. 代码

public function index1(Request $request){
    //将对象结果集转为数组结果集
    $data = User::orderBy('id','desc')->limit(6)->get()->toArray();
    //传入页数,默认值为1
    $page = $request->page ?? 1;
    //每页的条数
    $perPage = 2;
    //计算每页分页的初始位置
    $offset = ($page * $perPage) - $perPage;
    //实例化LengthAwarePaginator类,并传入对应的参数
    $data = new LengthAwarePaginator(array_slice($data, $offset, $perPage, true), count($data), $perPage,$page, ['path' => $request->url(), 'query' => $request->query()]);
    return $data;
}

3.2.2. 测试

符合我们的需求

array_paginate

3.2.3. 适用

比较适用于自建的数组想进行分页。

因为一开始就被转换为数组了,所以想要用模型中的方法是不可能了。

3.2.4. 缺点

1.无法使用对应模型里的方法。

2.内置的Api资源无法正常使用。

当我们的UserResource.php里的内容为这样时:

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id'=>$this->id,
            'name' => $this->name,
            'status' => UserEnum::getStatusName($this->status),
            'created_at'=>(string)$this->created_at,
            'updated_at'=>(string)$this->updated_at
        ];
    }
}

此时在控制器中使用Api资源来返回结果


return UserResource::collection($data);

报错,提示Trying to get property 'id' of non-object。这是由于我们传入到内置分页类中的是数组而不是一开始的对象形式,所以提示找不到这个属性。我们只需要进行一些稍微的修改。

public function toArray($request)
{
    return [
        'id'=>$this->resource['id'],
        'name' => $this->resource['name'],
        'status' => UserEnum::getStatusName($this->resource['status']),
        'created_at'=>(string)$this->resource['created_at'],
        'updated_at'=>(string)$this->resource['updated_at']
    ];
}

此时返回结果正常,但是不会报错。但是还是有很多问题,比如,无法使用数据包裹,条件关联等等。因为传入的分页是数组而不是对象导致的。

所以说,数组来进行分页,只适用于自己构建的数据数组。

3.3. 对象

接着解决上面的痛点,一开始我们就将对象结果集转为了数组结果集(百度上千篇一律都是转成了数组结果集),所以让导致模型方法以及Api资源都不能很好地使用。

现在我们不转换为数组,直接用对象结果集来进行自定义分页。

先来看一个函数


array_slice()

这是上面数组能进行分页的关键,可以指定从第几个元素开始,显示几个元素。

那对象结果集是否有类似的方法可以调用?这样我们就可以做出对象结果集的分页了。答案是有的。文档中的集合方法slice()拥有一样的功能。

3.3.1. 代码

public function index(Request $request){
    $data = User::orderBy('id','desc')->limit(6)->get();
    //传入页数,默认值为1
    $page = $request->page ?? 1;
    //每页的条数
    $perPage = 2;
    //计算每页分页的初始位置
    $offset = ($page * $perPage) - $perPage;
    //实例化LengthAwarePaginator类,并传入对应的参数
    $data = new LengthAwarePaginator($data->slice($offset,$perPage), count($data), $perPage,$page, ['path' => $request->url(), 'query' => $request->query()]);
    return UserResource::collection($data);
}

此时UserResource.php文件中的内容为

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id'=>$this->id,
            'name' => $this->name,
            'status' => UserEnum::getStatusName($this->status),
            'created_at'=>(string)$this->created_at,
            'updated_at'=>(string)$this->updated_at
        ];
    }
}

3.3.2. 测试

没有任何错误,符合我们的要求

object_paginate

3.3.3. 适用

非自建数组,想使用模型的方法或者使用Api资源。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 11

:+1: 小兄弟 手摸手系列什么时候开始啊

4年前 评论

@gotophp 大佬不是CEO吗

file

4年前 评论
Complicated

分页的逻辑不用自己写,可以是有 limit + forPage 这俩方法,我试过了,limit 对于 forPage是有效的

4年前 评论

@Complicated 但是会出现 第一页数据正常,第二页就数据格式变了

4年前 评论

第一页正常:

{
    "current_page": 1,
    "data": [
        {
            "id": 1613,
            "cs_merchant_id": 1000000020,
            "goods_name": "不知名超市分享区商品",
            "price": "2.00",
            "logo": "http:\/\/static.daqian520.com\/8039bf3c7db25e5e7c160689a4780672.jpg",
            "sq_type": 2,
            "sq_goods_profit": "25.00",
            "created_at": "2019-05-29 17:06:04"
        },
        {
            "id": 1614,
            "cs_merchant_id": 1000000029,
            "goods_name": "旗舰店分享区拼团商品",
            "price": "88.00",
            "logo": "http:\/\/static.daqian520.com\/d75681d5b7f6abe6c2ab35fb25f81c17.jpg",
            "sq_type": 2,
            "sq_goods_profit": "21.00",
            "created_at": "2019-05-29 17:47:26"
        }
    ],
    "first_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=1&pagesize=2",
    "from": 1,
    "last_page": 50,
    "last_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=50&pagesize=2",
    "next_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=2&pagesize=2",
    "path": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods",
    "per_page": "2",
    "prev_page_url": null,
    "to": 2,
    "total": 100
}

第二页数据格式怎么就变了呢

{
    "current_page": 2,
    "data": {
        "2": {
            "id": 1615,
            "cs_merchant_id": 1000000029,
            "goods_name": "旗舰店分享区拼团商品(商品名称要大于15个字测一个东西)",
            "price": "370.45",
            "logo": "http:\/\/static.daqian520.com\/2c1c104a9a5f1c6ddd97fb127d2ced74.jpg",
            "sq_type": 2,
            "sq_goods_profit": "20.00",
            "created_at": "2019-05-29 17:48:23"
        },
        "3": {
            "id": 1618,
            "cs_merchant_id": 1000000029,
            "goods_name": "凭什么全都有",
            "price": "378.00",
            "logo": "http:\/\/static.daqian520.com\/2cd6ca160f17b77709e95da6b53f1b5e.jpg",
            "sq_type": 2,
            "sq_goods_profit": "25.00",
            "created_at": "2019-06-04 12:30:20"
        }
    },
    "first_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=1&pagesize=2",
    "from": 3,
    "last_page": 50,
    "last_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=50&pagesize=2",
    "next_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=3&pagesize=2",
    "path": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods",
    "per_page": "2",
    "prev_page_url": "http:\/\/o.local.evlee.top\/api\/user\/v2\/ad\/handpickGoods?page=1&pagesize=2",
    "to": 4,
    "total": 100
}
4年前 评论
zhaoguangshuai 4年前
一个人的江湖 (作者) 4年前
Complicated
4年前 评论

@Complicated

$page = request('page', 1);
$pageSize = request('pagesize', 20);

$goods =  DB::table('cs_goods as c')
     ->where('status', CsGoods::STATUS_ON)
     ->limit(100)
     ->get();

$total = count($goods);
$goods = collect($goods)->forPage($page, $pageSize);

return $goods;
4年前 评论
Complicated

@一个人的江湖 forPage确实有这个毛病,,你这样写吧 where(...)->limit(100)->forPage()->get() 这样写

4年前 评论
一个人的江湖 4年前
$page = request('page', 1);
$pageSize = request('pagesize', 20);

$goods =  DB::table('cs_goods')->where('status', CsGoods::STATUS_ON)->limit(100)->get();

$total = count($goods);
$goods = collect($goods)->forPage($page, $pageSize);

return Result::success([
    'list' => $goods->values(),
    'total' => $total
]);

就像我上面说的,如果使用 forpage 来进行分页了,第一页的数据是正常的,第二页就会出现带下标的数据

这样会造成用户端数据会显示不出来甚至会报错,所以返回数据必须使用 $goods->values(),这样就正常了

原谅我五个月后才来完善这个问题 :joy: :joy: :joy:

4年前 评论

我也是 第一页正常,但是 第二页 会有下标数字

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!