Laravel Collection

Laravel custom collection pagination and how to paginate eager loading relationship

Lâu rồi mới biên một bài post mới phần vì lười đi nhiều cũng như ngập ngụa trong công việc 😅😅 hehe

Chuyện là hôm nay có làm quả eloquent query vào db với nội dung là lấy ra pagination cho relationship. Đại loại kiểu mối quan hệ many-to-many relationship lấy ví dụ trong bài viết này luôn.

$shop->load([
    'products' => function ($q) {
        $q->paginate();
    },
])

À ha, game là dễ phải không mọi người ha... hehe cho đến khi xem debugbar thì thấy ôi thôi duplicate query.
Hễ trong cái callback function kia cho thêm gì vào là nhân đôi lên

Duplicate query 😭😭

Ok. Chúng ta khoan hãy nói đến việc duplicate đi, xử lý sau. Quan trọng hơn là kết quả trả về không phải là dạng pagianate (có thể sử dụng kiểu {{ $shop->products->links() }} ngoài view) mà là một collection.

Đến đây thì cái tiêu đề mình đặt trên kia có vẻ vô lý mạc sầu nhưng lại rất thuyết mộ dung phục rồi đấy.

Thôi thì tầm này liêm sỉ gì nữa, chả hiểu là bug laravel hay trình mình gà, bắt tay vô xử lý từng cái vậy.

Đầu tiên là paginae, thực chất nó chỉ là dùng limit() và offset() thôi, vậy phân trang 15 items và đang ở page 3 thì ta sẽ có limit(15) và offset(30) -> (3 - 1) * 15 nhỉ.

Đoạn code phân trang ở trên sẽ viết lại thành ntn

$shop->load([
    'products' => function ($q) {
        $page = Paginator::resolveCurrentPage('page');
        $perPage = 15;
        $q->skip(($page - 1) * $perPage)->take($perPage);
    },
]);
Ahihi còn 5 rồi nè

Bây giờ kết quả trả về là một collection. Rồi giờ tiếp đến là trả về phân trang như khi dùng paginate, sau khi kiểm tra collection trong laravel không hề hỗ trợ paginate method nên mình đành phải tạo một cái vậy. Collection hỗ trợ macro function nên bạn hoàn toàn có thể inject một collection method bằng cách viết vào phương thức boot của app\Providers\AppServiceProvider.php

use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Collection::macro('paginate', function ($total = null, $perPage = 15, $pageName = 'page', $page = null) {
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
        $total = $total ?: $this->count();
        $items = $total ? $this : $this->forPage($page, $perPage);

        return new LengthAwarePaginator($items, $total, $perPage, $page, [
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => $pageName,
        ]);
    });

}

Ở trên thì mình có thêm vào 1 tham số total mặc định là null, paginate ở đây có 2 nghĩa. Nếu có total truyền vào thì ta hiểu là cái collection hiện tại đã được lấy về là số phần tử của một trang rồi, còn ngược lại k có thì hiểu là collection này chứa full items nhé.

Ở ví dụ trên thì sẽ dùng trường hợp 1 đó là truyền total vào, nên ta cần thêm tí chỉnh sửa nữa

$total = $shop->products()->count();

$shop->load([
    'products' => function ($q) {
        $page = Paginator::resolveCurrentPage('page');
        $perPage = 15;
        $q->skip(($page - 1) * $perPage)->take($perPage);
    },
]);

$shop->products = $shop->products->paginate($total);

Đấy xong rồi

Viết bài chỉ mong được nhiều like thôi, các bạn like cho mình đi 😍

Registration Login
Sign in with social account
or
Lost your Password?
Registration Login
Sign in with social account
or
A password will be send on your post
Registration Login
Registration