Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

masterakado's avatar

Laravel pagination not working with array instead of collection

I'm trying to paginate an array data set and it has proven more challenging than I thought.

I'm using Laravel 5

So I have an abstract interface/repository that all my other models extend to and I created a method inside my abstract repository call paginate. I've included both

use Illuminate\Pagination\Paginator;

and

use Illuminate\Pagination\LengthAwarePaginator;

Here is the method

  public function paginate($items,$perPage,$pageStart=1)
        {
            
            // Start displaying items from this number;
            $offSet = ($pageStart * $perPage) - $perPage; 
            
            // Get only the items you need using array_slice
            $itemsForCurrentPage = array_slice($items, $offSet, $perPage, true);
           return new LengthAwarePaginator($itemsForCurrentPage, count($items), 
           perPage,Paginator::resolveCurrentPage(), array('path' => Paginator::resolveCurrentPath()));
        }

So as you can imagine this function accepts an array of $items a $perPage variable that indicates how many to items to paginate and a $pageStart that indicates from which page to start.

The pagination works and I can see LengthAwarePaginator instance when I'm doing a dd() , all of it's values seem fine.

The problem starts when I'm displaying the results.

When I do {!! $instances->render() !!} The paginator links display's fine, the page parameter changes according to links but the data is not changing. Data is the same in every page. When I'm using Eloquent for example Model::paginate(3) everything works fine, but when I dd() this LengthAwarePaginator it's identical to the LengthAwarePaginator instance of the my custom paginator with the exception that it paginates an array ofcourse and not a collection.

0 likes
35 replies
bobbybouwmann's avatar

I had this problem as well and this seems to fix it

$page = Input::get('page', 1); // Get the current page or default to 1, this is what you miss!
$perPage = 20;
$offset = ($page * $perPage) - $perPage;

return new LengthAwarePaginator(array_slice($array, $offset, $perPage, true), count($array), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()]);

Replace $array with your data. We also add the path and query stuff to make sure that other parameters are used as well in the url, let's say you have a view where you can sort as well that data would be lost! This doesn't happen if you do it like this

8 likes
bugsysha's avatar

Why are you making paginate method? Why don't you use it standard way?

$items = Item::where('draft', 1)
                         ->orderBy('created_at', 'desc')
                         ->paginate(12);

Forward it to view and just call

{!! $items->render() !!} 
bobbybouwmann's avatar

@bugsysha Well if you start to group stuff in Eloquent the paginator will fail! It can't get the count correct anymore, in that case you need to call your own paginator ;)

masterakado's avatar

@bobbybouwmann you were right I wasn't passing the 'page' parameter. I don't know who I missed that.

@bugsysha I'm not using the default paginate method because I'm not using Eloquent. All my queries just use the query builder which does not return a collection, it returns an array instead.

shangsunset's avatar

hey @bobbybouwmann i did as the solutions suggests but i couldnt get navigation link to display. any sugguestions?

    view()->composer('layouts.book', function($view)
    {
        
        $pages = [];
        foreach($book->textsection_pages as $textsection) {
            $pages[] = $textsection->alias;
        }
        foreach($book->task_pages as $task) {
            $pages[] = $task->alias;
        }
        foreach($book->tutor_pages as $tutor) {
            $pages[] = $tutor->alias;
        }
        foreach($book->eval_pages as $eval) {
            $pages[] = $eval->alias;
        }

        $chapters = $book->chapters()->orderBy('weight', 'asc')->get();
        $paginated = $this->paginate($pages, 10);
        $view->with(['chapters' => $chapters, 'book' => $book, 'paginated' => $paginated]);

      });

 public function paginate($array, $perPage, $pageStart=1) {

    $offset = ($pageStart * $perPage) - $perPage;

    return new Paginator(array_slice($array, $offset, $perPage, true), $perPage, $pageStart,
              [
            'path' => Paginator::resolveCurrentPath(),
             ]
    );
}

and in view {!! $paginated->render() !!}

Francismori7's avatar

Here's mine if it helps anybody:

    /**
     * Paginates a collection of clients.
     *
     * @param Collection $allClients The collection containing all clients.
     * @return LengthAwarePaginator
     */
    protected static function paginateClients(Collection $allClients)
    {
        $currentPage = LengthAwarePaginator::resolveCurrentPage() ?: 1;
        $startIndex = ($currentPage * self::MAX_CLIENTS_PER_PAGE) - self::MAX_CLIENTS_PER_PAGE;
        $paginatedClients = Collection::make($allClients)->slice($startIndex, self::MAX_CLIENTS_PER_PAGE);

        /*
         * Eager load orders for each client, but we don't want those cached.
         */
        if (!$paginatedClients->isEmpty()) {
            $query = $paginatedClients->first()->newQuery()->with('orders');
            $paginatedClients = Collection::make($query->eagerLoadRelations($paginatedClients->all()));
        }
        return new LengthAwarePaginator(
            $paginatedClients,
            $allClients->count(),
            self::MAX_CLIENTS_PER_PAGE,
            $currentPage, 
            [
                'path' => LengthAwarePaginator::resolveCurrentPath(),
            ]
        );
    }
1 like
shangsunset's avatar

Hey @Francismori7 , i can get the number of elements of a collection to display on a page as specified by perPage , but cant get the navigation links to appear. any reason why this is happening?

Francismori7's avatar

How many records are there? If you have less than the number of records per page to display, no links will display. For instance, if you have a per-page of 10, and the query returns 9 or 10 rows, you will not get any navigation links. If however you get 11 rows, they will show.

Can you confirm that?

EDIT: Also, are you using a Paginator or a LenghtAwarePaginator? The latter will have page numbers in ->render(), the first one will only have previous and next links.

shangsunset's avatar

@Francismori7 there are 223 records in the collection and im using Paginator. also, im doing this in a viewComposerProvider if it matters.

Francismori7's avatar

You get the results properly though? Like, the items.

Can you dd() the Paginator? Also.. $pageStart makes no sense in your code? You are always on the first page that way?

Also, could you explain the whole issue? Maybe you are over-thinking this, we can maybe find a better solution than a custom pagination (take me for example, I just dropped my custom pagination totally and switched to Eloquent when I realized that my caching could break with 1m+ rows, even if it was very tricky)

shangsunset's avatar

yea i can get the items.

Paginator {#500 ▼
  #hasMore: false
  #items: Collection {#823 ▼
      #items: array:5 [▼
  0 => TextsectionPage {#445 ▶}
  1 => TextsectionPage {#446 ▶}
  2 => TextsectionPage {#931 ▶}
  3 => TextsectionPage {#932 ▶}
  4 => TextsectionPage {#933 ▶}
  ]
 }
  #perPage: 5
  #currentPage: 1
  #path: "http://localhost:8000/library/precalc/precalc_numbers_null_equations_identities/"
  #query: []
 #fragment: null
 #pageName: "page"
}
    

im trying to implement a view like sphinx documentation where you can click on next and previous to navigate between pages links. i pass an array of items that contain links of pages from a viewComposer to a layout view.

Francismori7's avatar

In your output, you have: #hasMore: false

It appears when you instanciate your Paginator, you don't properly pass the count of "all items". This is because a Paginator instance (not LengthAwarePaginator) needs ALL the items, it will execute the slicing itself, from what I can see, however... it looks to be slicing from a 0-offset...

Take a look at vendor/laravel/framework/src/Illuminate/Pagination/Paginator@checkForMorePages()

It is counting the number of items you are passing in the constructor and comparing it to the max per-page value. If the data is already sliced... of course this property (hasMore) will be set to false since 5 == 5. Try not slicing in the constructor. But then the other issue is the Paginator itself does not appear to slice anything either...

Francismori7's avatar

Looking at simplePaginate(), I get the sense that you should pass 6 items in the Paginator if you want 5. See:

public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page')
    {
        $page = Paginator::resolveCurrentPage($pageName);

        $perPage = $perPage ?: $this->model->getPerPage();

        $this->skip(($page - 1) * $perPage)->take($perPage + 1); // 5 per page + 1 = 6? Query will return 6 rows.

        return new Paginator($this->get($columns), $perPage, $page, [ // $this->get() runs the query, so gets 6 items.
            'path' => Paginator::resolveCurrentPath(),
            'pageName' => $pageName,
        ]);
    }

Try to pass one more item to your Paginator constructor. It appears like checkForMorePages() will slice off the last item.

jekinney's avatar

You can merge collections too. You don't need to pass in an array.

Francismori7's avatar

@jekinney No need, it is already parsed as a Collection in the Paginator if it is not a Collection already. Cleans up his code.

shangsunset's avatar

@Francismori7

i tried not slicing, #hasMore became True and the navigation links appeared. but the records were all the same on every page.

now i think maybe pagaination isnt right for this situation. i want the next and previous links direct people to different urls instead of ?page=1/2/3. for example, the first page has url: /example/foo. when user clicks on next link, the url should change completely, like /example/bar instead of /example/foo?page=2

is there a good approach to generate the 'next url'. thanks

Francismori7's avatar

@shangsunset The main issue here was about Paginator, which requires n+1 items sliced, and depending on the page, you get offsets +5 too.

Page 1: LIMIT 6 OFFSET 0
Page 2: LIMIT 6 OFFSET 5
Page 3: LIMIT 6 OFFSET 10

And so on, when the last page is reached, it will not display a next page link.

DNAngel's avatar

I have a few things that I don't understand with this script from @Francismori7:

    /**
     * Paginates a collection of clients.
     *
     * @param Collection $allClients The collection containing all clients.
     * @return LengthAwarePaginator
     */
    protected static function paginateClients(Collection $allClients)
    {
        $currentPage = LengthAwarePaginator::resolveCurrentPage() ?: 1;
        $startIndex = ($currentPage * self::MAX_CLIENTS_PER_PAGE) - self::MAX_CLIENTS_PER_PAGE;
        $paginatedClients = Collection::make($allClients)->slice($startIndex, self::MAX_CLIENTS_PER_PAGE);

        /*
         * Eager load orders for each client, but we don't want those cached.
         */
        if (!$paginatedClients->isEmpty()) {
            $query = $paginatedClients->first()->newQuery()->with('orders');
            $paginatedClients = Collection::make($query->eagerLoadRelations($paginatedClients->all()));
        }
        return new LengthAwarePaginator(
            $paginatedClients,
            $allClients->count(),
            self::MAX_CLIENTS_PER_PAGE,
            $currentPage, 
            [
                'path' => LengthAwarePaginator::resolveCurrentPath(),
            ]
        );
    }

Why is the paginateClients() is protected instead of public? I thought static should be in public. Also, I included public static $MAX_CLIENTS_PER_PAGE = 1 and added a string to all the self::MAX_CLIENTS_PER_PAGE becoming self::$MAX_CLIENTS_PER_PAGE. Am I doing it right?

Francismori7's avatar

@DNAngel I made it protected because it was only getting accessed from the very same controller it was declared in.

The MAX_CLIENTS_PER_PAGE "variables" are constants, not properties, hence why they do not have a $ symbol:

class ClientsController extends Controller
{
    /**
     * A page can have a maximum of x clients.
     */
    const MAX_CLIENTS_PER_PAGE = 10;
    /**
     * A client page can have a maximum of x orders.
     */
    const MAX_ORDERS_PER_CLIENT_PAGE = 10;

    /* Rest of class */
}
DNAngel's avatar

@Francismori7 Thanks for replying my message.

I applied your paginateClients() in the HomeController.php

$allStatuses = PaginateController::paginateClients($allStatuses); so I have to change the protected to public. Would that be a problem?

class HomeController extends Controller {

    public function index(Request $request) {

        if(Auth::check()) {

            $statuses = Status::NotReply()->NotFriendPostUserProfile()->where(function($query) {
                return $query->where('user_id', Auth::user()->id)
                            ->orWhereIn('user_id', Auth::user()->friends()->lists('id'));
            });

            $friendPosts = Status::NotReply()->FriendPostUserProfile()->where(function($query) {
                return $query->where('user_id', Auth::user()->id)
                            ->orWhereIn('user_id', Auth::user()->friends()->lists('id'));
            });

            $collection = collect($statuses->get());
            $allStatuses = $collection->merge($friendPosts->get())->sortByDesc('created_at');

            $allStatuses = PaginateController::paginateClients($allStatuses);

            if($request->ajax()) {

                return view('timeline.ajax.index')->with('allStatuses', $allStatuses)->render();
            }

            return view('timeline.index')->with('allStatuses', $allStatuses);
        }

        return view('home');
    }
}

Also, could you please explain the line below from your script:

if (!$paginatedClients->isEmpty()) {
        $query = $paginatedClients->first()->newQuery()->with('orders');
        $paginatedClients = Collection::make($query->eagerLoadRelations($paginatedClients->all()));
    }

I could not get it to work unless I deleted the with('orders'). It gives me undefined error. Your script is awesome, but I really want to fully understand it.

Francismori7's avatar

@DNAngel It wouldn't be a problem no.

The code you quoted on your post is related to eager loading. You load your model's relationships. In your case, you probably have something along the lines of $status->user or $status->comments, well you could use them there to load them properly from your database without using N+1 queries. See eager loading.

douglas_quaid's avatar

@bobbybouwmann I did exactly what you said and it works, however, my paginator is spitting back the indicies (the 60:), which is not what I want like so:

"data": {
    "60": {
        "title": "Perferendis esse et facilis et voluptas.",
        "text": "Quisquam numquam earum molestiae debitis. Voluptatum nostrum aperiam sit ad. Voluptatem voluptas ea voluptatem unde porro aut possimus nihil. Et quis et sed. Perspiciatis ut ut voluptatem sit. Nemo quasi officiis nobis provident.",
        "image_path": "example/filepath",
        "video_id": "139399130",
        "tags": []
    },
    ...
}

Instead, I want this:

"data": {
    {
        "title": "Perferendis esse et facilis et voluptas.",
        "text": "Quisquam numquam earum molestiae debitis. Voluptatum nostrum aperiam sit ad. Voluptatem voluptas ea voluptatem unde porro aut possimus nihil. Et quis et sed. Perspiciatis ut ut voluptatem sit. Nemo quasi officiis nobis provident.",
        "image_path": "example/filepath",
        "video_id": "139399130",
        "tags": []
    },
    ...
}

How do I remove the indicies from the array_slice() method?

UPDATE:: IT looks like the culprit is the 4th paramater supplied to array_slice for preserving keys

http://php.net/manual/en/function.array-slice.php

I set to false and it works. Is it a problem if I set this to false?

array_slice($data, $offset, $perPage, true)
illuminator94's avatar

@bobbybouwmann i did as you told on the controller.

public function paginate($pagination, $perPage) { $page = Input::get('page', 1); $perPage = 20; $offset = ($page * $perPage) - $perPage;

return new LengthAwarePaginator(array_slice($pagination, $offset, $perPage, false), count($pagination), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()]);

}

But how do i get this to work on my view? my data is on $pagination variable.

Please help me.

Regards

Next

Please or to participate in this conversation.