Page 1 of 1

Eclipse Pagination

Posted: Fri Jun 18, 2004 9:16 pm
by McGruff
Have been thinking: why not encapsulate pagination arithmetic in its own class ("Indexer")? The class might look like this:

Code: Select all

/*
    CLASS Indexer

    The simple arithmetic required for pagination-type operations.
    A (modified) iterator would seek to the getOffset() value. 
    isValid() would be called per iteration.
*/
class Indexer
{
    var $i = 0;

    /*
        param (integer) $total_rows
        param (integer) $subset_size ie max rows per page
    */
    function Indexer($total_rows, $subset_size)
    {
        $this->total_rows  = $total_rows;
        $this->subset_size = $subset_size;
    }
   
    /*
        param (integer)
        return (integer)
    */
    function getOffset($page_number = 1)
    {
        return ($this->_pageNumber($page_number) - 1) * $this->subset_size;
    }

    /*
        Simple counter called once per iteration.     
        return (bool)
    */
    function isValid()
    {
        return (++$this->i < $this->subset_size);
    }

    /*
        In addition to its use in _pageNumber(), this method would also be used for pagination hyperlink creation & hence isn't "private".
        return (integer)
    */
    function getNumPages()
    {
        return ceil($this->total_rows/$this->subset_size);
    }

   
    //////////////////////////////////////////
    //              PRIVATE                 //
    //////////////////////////////////////////


    /*
        Filter invalid page numbers.
        return (integer)
    */
    function _pageNumber($page_number)
    {
        if($page_number <= 0)
        {
            return 1;

        } elseif($page_number > $num_pages = getNumPages()) {

            return $num_pages;
       
        } else {
       
            return $page_number;
        }
    }


}
For the next bit, a minor modification to standard iterators is required. Rather than resetting to the 0 row every time, "seeking iterators" can be reset to any row index by passing an $offset arg to the reset() method (see below).

Setting that aside for a moment, here's Indexer in use:

Code: Select all

/*
        $it (object) - a "seeking" query iterator
        $indexer (object) - instance of Indexer, above
        $page (integer) - a page number
    */
    for($it->reset($indexer->getOffset($page)); ($it->isValid() and $indexer->isValid()); $it->next())
    {
        // etc
    }
$indexer->getOffset() allows the iterator to be reset to the desired offset. $indexer->isValid() 'observes' the iteration, counting out each loop until the page limit is reached.

In this way PagedQuery & PagedQueryResult can be replaced with a smaller, simpler and more focussed Indexer class alongside the normal (almost..) iterators.

Indexer deals with all the pagination arithmetic and nothing else. In the end that's all pagination is: some simple arithmetic. Perhaps this is expressed more clearly in its own, dedicated class.


Seeking Iterators

QueryIterator modified as a "Seekerator":

Code: Select all

/*
         param (integer) $offset
    */
    function reset($offset = 0) 
    {
        $this->query_result->seek($offset);
        $this->index = $offset;
    }
Then refactor QueryResult->getRow to separate out seeking and fetching:

Code: Select all

function getRow($index, $type = ECLIPSE_DB_BOTH) 
    {
        $this->seek($index);
        ++$this->currentRow;
        
        switch ($type)
        {
            case ECLIPSE_DB_ASSOC:
                return mysql_fetch_assoc($this->getResultId());
            case ECLIPSE_DB_NUM:
                return mysql_fetch_row($this->getResultId());
            case ECLIPSE_DB_BOTH:
            default:
                return mysql_fetch_array($this->getResultId());
        }
    }

    function seek($index) 
    {        
        if ($index != $this->currentRow) 
        {
            mysql_data_seek($this->getResultId(), $index);
            $this->currentRow = $index;
        }
    }