Paginate for a collection, Laravel

Viewed 3077

I try to add some new values to each user from foreach, but because I use get, now I can't use paginate on response, but I also need to add that values to each user. Any ideas?

public function statistics()
    {
        $users = User::select(['id', 'name'])->get();
        foreach ($users as $key => $user) {
            $history = AnswerHistory::where('user_id', '=', $user->id)->get();
            $user->total_votes = count($history);
            $user->total_time = gmdate("H:i:s", ($history->sum('answer_time')));
        }

        return response()->json($users);
    }
3 Answers

what you want is not possible in laravel by default, however there are a few things you can do.

Solution one you can return paginator first and then modify the collection.

    $users = User::select(['id', 'name'])->paginate(4)->toArray();

    $users['data'] = array_map(function ($user) {
            $history = AnswerHistory::where('user_id', '=', $user->id)->get();
            $user->total_votes = count($history);
            $user->total_time = gmdate("H:i:s", ($history->sum('answer_time')));
        return $user;
    }, $users['data']);

    return  $users;

Solution two The macro way. If you prefer, add the Collection macro to a Service Provider. That way you can call paginate() on any collection: See AppServiceProvider.php for a sample implementation.

    public function boot()
    {
        Collection::macro('paginate', function ($perPage, $total = null, $page = null, $pageName = 'page') {
            $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);

            return new LengthAwarePaginator(
                $this->forPage($page, $perPage),
                $total ?: $this->count(),
                $perPage,
                $page,
                [
                    'path' => LengthAwarePaginator::resolveCurrentPath(),
                    'pageName' => $pageName,
                ]
            );
        });
    }

and then your code will be like this

        $users = User::select(['id', 'name'])->get();
        foreach ($users as $key => $user) {
            $history = AnswerHistory::where('user_id', '=', $user->id)->get();
            $user->total_votes = count($history);
            $user->total_time = gmdate("H:i:s", ($history->sum('answer_time')));
        }

        return response()->json($users->paginate(4));

Solution three The subclass way. Where you want a "pageable" collection that is distinct from the standard Illuminate\Support\Collection, implement a copy of Collection.php in your application and simply replace your use Illuminate\Support\Collection statements at the top of your dependent files with use App\Support\Collection:

<?php

namespace App\Support;

use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection as BaseCollection;

class Collection extends BaseCollection
{
    public function paginate($perPage, $total = null, $page = null, $pageName = 'page')
    {
        $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);

        return new LengthAwarePaginator(
            $this->forPage($page, $perPage),
            $total ?: $this->count(),
            $perPage,
            $page,
            [
                'path' => LengthAwarePaginator::resolveCurrentPath(),
                'pageName' => $pageName,
            ]
        );
    }
}

and your code will be like this

// use Illuminate\Support\Collection
use App\Support\Collection;

        $users = User::select(['id', 'name'])->get();
        foreach ($users as $key => $user) {
            $history = AnswerHistory::where('user_id', '=', $user->id)->get();
            $user->total_votes = count($history);
            $user->total_time = gmdate("H:i:s", ($history->sum('answer_time')));
        }

        return response()->json((new Collection($users))->paginate(4);

According to your post, User has many AnswerHistory. You can build relationship between them.

So getting the total_votes and total_time by withCount:

$users = User::withCount('answerHistories AS total_votes')
     ->withCount(['answerHistories AS total_time' => function($query) {
           $query->select(DB::raw("SUM(answer_time)"));
     }])->paginate(10);

And you can get the pagination datas by getCollection, and change the datas inside:

$users->getCollection()->transform(function ($data) {
    $data->total_time = gmdate('H:i:s', $data->total_time);
    return $data;
});

You can create pagination by yourself look to this Laravel doc https://laravel.com/docs/7.x/pagination#manually-creating-a-paginator. I will suggest to use LengthAwarePaginator Here is some code example with array

    // creating pagination
    $offset = max(0, ($page - 1) * $perPage);
    $resultArray = array_slice($result, $offset, $perPage);
    $paginator = new LengthAwarePaginator($resultArray, count($result), $perPage, $page);
    $paginator->setPath(url()->current());
    $paginator->appends(['per_page' => $perPage]);

    return response()->json([
        'message' => 'Success',
        'data'    => $paginator
    ]);

But I think your case have better "good" solution, you can load AnswerHistory with hasMany Laravel relation and with function.

Related