Sneaky Drupal Pagers

See the update at the bottom!
Drupal’s pagers are neat, and when they were first developed, were way ahead of their time. They also have a couple problems. One of them is scalability. When you’ve got 10,000,000 somethings, calculating how many pages there are so that you can skip to the last one is time consuming.
Another limitation is that the pager is designed to page over a database query. The Apache Solr Search module uses Drupal pagers to move through pages of search results that come from Solr. The pseudo code for getting this to work looks like this:
<?php
// What result do we want to start on?
$offset = $page * $number_per_page;
// How many search results are there in total?
$total = $result->get_total();
// Send a very simple database query to the pager system to trick it.
pager_query("SELECT %d", $offset, 0, NULL, $total);
// Magic happens here. A pager appears.
$output .= theme('pager');
?>That’s great! But I recently had a case where it was impossible to tell how many results there are in the total set. What is really needed is the ability to advance the pager until there aren’t any more pages, but Drupal doesn’t support anything like this by default. Twitter does it, but Drupal… meh. It’s sneaky time!
<?php
// Remember: http://is.gd/3Sf9Z
$total = 0;
// Note that $page is zero based (page zero is the first page).
// Note also that count($results) is just one page's worth of results,
// not the entire possible set (which is impossible to calculate).
// If there are fewer results than what we want to show per page,
// we know we've come to the end of the result set, and don't need
// to show any more pages.
if (count($results) < $number_per_page) {
$total = $number_per_page * ($page + 1);
}
// Otherwise, we want to tell the pager to give us yet another
// page to go to.
else {
$total = $number_per_page * ($page + 2);
}
// Now the pager will either end where we are, or add one
// more page to the end. This way you can keep advancing one
// more page until there are no more results left.
pager_query("SELECT %d", $number_per_page, 0, NULL, $total);
$output .= theme('pager');
?>This strategy could be applied to both of the problem cases I mentioned above. If you have a HUGE result set and need a pager, and don’t want to destroy your database, this is a viable technique. It also works if you’re getting your results from a source that can’t tell you how many results there are in total. And it’s sneaky. Enjoy.
Update:
Instead of using a database query to manipulate the page you can manipulate the globals instead:
$GLOBALS[‘pager_page_array’][] = 1; //what page you are on
$GLOBALS[‘pager_total’][] = 3; // total number of pages
$items_per_page = 50;
print theme(‘pager’, NULL, $items_per_page);
Thanks Chx for the tip!
Related Content
AcquiaBlog

2010 has been an inflection point for the Acquia partner program. We are doing more business than ever with partners, including case studies with Palantir.net, Blink Reaction, and IBM Global Services.
Bryan House
It is that phase of my life! I'm just turning 30 in a month, working with Drupal for 7 years and just had my third Acquia anniversary a week ago. Time to look back and evaluate how things went, all the good and bad things; even better if the wisdom can be shared with others. This was part of my thinking when I submitted the session titled "Come for the software, stay for the community" for Drupalcon Copenhagen.
Gábor Hojtsy
It sounded like a really simple request: "Is it easy to add a search filter for 'My posts'?". In other words, add a search result facet for posts by the current (logged in) user through the Apache Solr Search Integration module APIs?
But then the wheels start turning - we want not just one blind link, but a real facet link that tells us how many results we'll get. Also, if we are filtering by 'My posts' then we probably have an equal use case for the opposite filter 'Posts not by me'. So we really need a facet block with two links and facets counts.
Peter Wolanin







Comments
Chris Shattuck
This is almost as sneaky as
This is almost as sneaky as hiding a pesky RSS icon with display:none. I assume that using a COUNT() query would be too expensive or inflexible in most cases? Now, would this solution work if the number of items on the last page was equal to the number of results per page?
Cheers!
Chris
Robert Douglass
Well, I think it's even more
Well, I think it's even more sneaky than display:none... but I guess the sneaky quotient can be debated.
In my case there is no COUNT() that can be done because I'm paging over something that isn't a database table, and it is impossible or impractical to know at the time you're generating the pager, how many results are in the total set. It might be 10, it might be 10 thousand.
Yes, unless I've coded wrongly, if the number of items on the last page is equal to the number you want to see per page, it will display the full last page and not allow you to page further.
Robert Douglass
Senior Drupal Advisor, Acquia
Robert Douglass
Chx has let me know that it
Chx has let me know that it is better to just manually manipulate the globals that store the pager values. See this change in Drupal core that essentially replaced the sneak query with global action.
My point was only half about generating pagers. The more important part, to me, was making a pager that doesn't know its ending point, that doesn't know how many results there are in the total set.
Robert Douglass
Senior Drupal Advisor, Acquia
Robert Douglass
Correction - the linked diff
Correction - the linked diff above comes from the paging module: http://drupal.org/project/paging
Robert Douglass
Senior Drupal Advisor, Acquia
Could you PLEASE fix the
Could you PLEASE fix the article as I asked in email? It's spreading already and it's incredibly bad practice!
Another example is http://cvs.drupal.org/viewvc.py/drupal/contributions/tricks/pager_withou... here.
Also it's bad practice that I can't leave anonymous comments here... that's why I mailed you originally instead of bothering with logging in just to leave a comment.