Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Paginator Component Proposal

Proposed Component Name Zend_Paginator
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Paginator
Proposers Jurriën Stutterheim
Matthew Ratzloff
Zend Liaison Ralph Schindler
Revision 1.0 - April 3, 2008: Initial proposal
1.1 - May 10, 2008: Merging with Matthew's proposal
2.0 - June 10, 2008 Major rewrite complete
2.1 - June 24, 2008: Update to match the latest SVN copy
2.2 - July 4, 2008: Update the proposal to reflect new naming (wiki revision: 45)

Table of Contents

1. Overview

Zend_Paginator provides a generic way to paginate through various collections of data, display the results, and render pagination controls.

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • This component will be able to paginate arrays, database queries, and iterators.
  • This component will fetch only those results from the database that need to be displayed.
  • This component will be compatible with Zend_View_Helper_Partial.
  • This component will provide its own view helper, built on the view partial functionality.
  • This component will provide an abstract adapter class to allow for custom adapters.
  • This component will provide a scrolling style interface to allow for custom scrolling styles.
  • This component will provide a factory class to make pagination even easier.

4. Dependencies on Other Framework Components

  • Zend_Exception
  • Zend_View_Helper_Partial

And optionally the following:

  • Zend_Controller_Router_Interface
  • Zend_View_Interface
  • Zend_Controller_Action_Helper_ViewRenderer

5. Theory of Operation

Each collection type has its own adapter which implements Zend_Paginator_Adapter_Interface. It provides a generic interface to provide all necessary methods needed to work with paginated collections.

The data that needs to be paginated is provided in the constructor. The amount of items per page and of course the current page can be set through the corresponding methods. Each specific pagination class returns a different collection result for a page, but each of those can be manipulated in the same way.

It is worth noting that the DbSelect paginator only fetches the required rows. It does this by adding a LIMIT to the query. In order to make this work, it clones the provided SELECT query and replaces the columns with count statement. Example:

The result of this query is one row containing one column called "zend_paginator_row_count". This is used internally to provide the record count. Optionally you can provide the adapter with a custom select query by using the setCountSelect() method.

This component will be compatible with Zend_View_Helper_Partial for maximum flexibility in choosing how to render pagination controls. It will also provide its own view helper built on top of the view partial functionality.

6. Milestones / Tasks

  • Milestone 1: [DONE] Write initial proposal
  • Milestone 2: [DONE] Review by community
  • Milestone 3: [DONE] Review by Zend
  • Milestone 4: [DONE] Component incubated
  • Milestone 5: [DONE] Write unit tests
  • Milestone 6: [DONE] Write documentation
  • Milestone 7: [DONE] Component cored

7. Class Index

8. Use Cases

UC-01: Paginate an array
UC-02: Paginate a database query

Optionally you can provide your own select query to fetch the item count.

UC-03: Paginate an iterator
UC-04: Rendering view script
UC-05: Search pagination control

http://www.builtfromsource.com/svn/Zend_Paginator/trunk/demos/Zend/Paginator/application/views/scripts/_partials/search_pagination_control.phtml

UC-06: Item pagination control

http://www.builtfromsource.com/svn/Zend_Paginator/trunk/demos/Zend/Paginator/application/views/scripts/_partials/item_pagination_control.phtml

UC-07: Dropdown pagination control

http://www.builtfromsource.com/svn/Zend_Paginator/trunk/demos/Zend/Paginator/application/views/scripts/_partials/dropdown_pagination_control.phtml

9. Class Skeletons

Please see http://framework.zend.com/svn/framework/standard/library/Zend/ for the latest code.

]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

<p><ac:image ac:width="84"><ri:url ri:value="http://www.rotaryclubofsantamonica.org/Images/2001-2002/Past%20Events/Schwarzenegger.jpg" /></ac:image> <em>Come with me if you want to paginate.</em></p>

Labels:
paging paging Delete
pager pager Delete
zend_paginator zend_paginator Delete
pagination pagination Delete
paginator paginator Delete
proposals proposals Delete
proposal proposal Delete
zend_paginate zend_paginate Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. May 12, 2008

    <p>Accuse me of painting the bikeshed, but I suggest naming the component <code>Zend_Pager</code>. It's shorter and still makes sense semantically.</p>

    1. May 15, 2008

      <p>A pager is also known as a mobile device that recieves numeric or text messages.</p>

      <p>Paginate more clearly covers the load IMHO. But then, I'm colorblind <ac:emoticon ac:name="wink" /></p>

    2. Jun 10, 2008

      <p>Quality is in the details; nothing wrong with some bike shed painting if it makes the product better.</p>

      <p>But to me, "pagination" is more intuitive than "paging". The typical verb form is "paginate" and not "page" for reasons of clarity. My preferred name would be Zend_Paginator (which would put it in line with Rails and others), but given the names of other components (Validate, Translate) we settled on Paginate. Should any movement happen on naming conventions we would of course revisit that decision.</p>

  2. May 15, 2008

    <p>How does Zend_Paginate relate to Zym_Paginate? Is the Zym version the lab/incubator version or just inspiration?</p>

    1. May 19, 2008

      <p>Jurriën and I are currently working jointly on something that is based on Zym_Paginate, but hopefully more flexible. <ac:emoticon ac:name="smile" /></p>

  3. May 30, 2008

    <p>IMHO, Zend_Paginate should do less. The purpose of paginate component is to generate page (separator) sequence (1 2 3 ... 5 ... 10 ... 12) (... - is separator) , provide API for next/previous page checking (isFirst, getFirst, hasNext, getNext, etc.) and to setup correct offset/limit pair. The data fetching functionality should be refactored to other component called like Zend_Data_Filter, which supports fetching/slicing data according to offset/limit parameters. </p>

    <p>Something like:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    <?php

    /**

    • Classes
      */

    /**

    • Zend_Data_Filter_Abstract
      *
    • @package Zend_Data_Filter
      */
      abstract class Zend_Data_Filter_Abstract {

    /**

    • Returns Item array
      *
    • @param integer $offset
    • @param integer $limit
    • @return array
      */
      abstract public function getItemArray($limit = null, $offset = null);

    /**

    • Returns Item count
      *
    • @return integer
      */
      abstract public function getItemCount();

    }

    /**

    • MyFilter
      *
    • @package Zend_Data_Filter
      */
      class MyFilter extends Zend_Data_Filter_Abstract {

    /**

    • Returns Item array
      *
    • @param integer $offset
    • @param integer $limit
    • @return array
      */
      public function getItemArray($limit = null, $offset = null)
      Unknown macro: { // Return Objects }

    /**

    • Returns Item count
      *
    • @return integer
      */
      public function getItemCount()
      Unknown macro: { // Return Object Count }

    }

    /**

    • Zend_Paginator
    • @package Investobiz_Paginator
      */
      class Investobiz_Paginator {

    /**

    • Paginator Strategy
    • @var Zend_Paginator_Strategy_Abstract
      */
      private $_strategy;

    /**

    • Item count
    • @var integer
      */
      private $_itemCount;

    /**

    • Item per Page
    • @var integer
      */
      private $_itemPerPage;

    /**

    • Current Item Page
    • @var integer
      */
      private $_itemPage;

    /**

    • Item Page count
    • @var integer
      */
      private $_itemPageCount;

    /**

    • Constructs Object
    • @param Zend_Paginator_Strategy_Abstract $strategy
    • @param integer $itemCount
    • @param integer $itemPerPage
    • @param integer $itemPage
      */
      public function __construct($strategy, $itemCount, $itemPerPage, $itemPage) {
      // Construct
      $this->_itemCount = $itemCount;
      $this->_itemPerPage = $itemPerPage;
      $this->_itemPage = $itemPage;
      $this->_itemPageCount = floor(($this->_itemCount - 1) / $this->_itemPerPage) + 1;

    // Adjust
    if (empty($this->_itemPage) || ($this->_itemPage < 1) || ($this->_itemPage > $this->_itemPageCount))

    Unknown macro: { $this->_itemPage = 1; }

    }

    /**

    • Sets Paginator Strategy
    • @param Zend_Paginator_Strategy_Abstract $strategy
    • @return void
      */
      public function setStrategy($strategy)
      Unknown macro: { $this->_strategy = $strategy; }

    /**

    • Returns Pagination
    • @return Zend_Paginator_Item_Abstract[]
      */
      public function paginate()
      Unknown macro: { return $this->_strategy->paginate( $this->_itemPageCount, $this->_itemPage, $this->_callback ); }

    /**

    • Returns Current Page
    • @var integer
      */
      public function getPage()
      Unknown macro: { return $this->_itemPage; }

    /**

    • Returns Page Count
    • @var integer
      */
      public function getPageCount()
      Unknown macro: { return $this->_itemPageCount; }

      /**

      • Return Item Count
      • @return integer
        */
        public function getItemCount()
        Unknown macro: { return $this->_itemCount; }

      /**

      • Checks if Current Page is First
      • @return boolean
        */
        public function isFirst()
        Unknown macro: { return ($this->_itemPage == 1); }

      /**

      • Returns First Page Number
      • @return int
        */
        public function getFirst()
        Unknown macro: { return 1; }

      /**

      • Checks if Current Page is Last
      • @return boolean
        */
        public function isLast()
        Unknown macro: { return ($this->_itemPage == $this->_itemPageCount); }

      /**

      • Returns Last Page Number
      • @return int
        */
        public function getLast()

    /**

    • Checks if Current Page has Next Page
    • @return boolean
      */
      public function hasNext()
      Unknown macro: { return ($this->isLast() == false); }

    /**

    • Returns Next Page
    • @return int
      */
      public function getNext()
      Unknown macro: { return ($this->_itemPage + 1); }

    /**

    • Checks if Current Page has Previous Page
    • @return boolean
      */
      public function hasPrevious()
      Unknown macro: { return ($this->isFirst() == false); }

    /**

    • Returns Previous Page
    • @return int
      */
      public function getPrevious()
      Unknown macro: { return ($this->_itemPage - 1); }

    /**

    • Returns Current Offset
    • @return integer
      */
      public function getOffset()
      Unknown macro: { return (($this->_itemPage - 1) * $this->_itemPerPage); }

    /**

    • Returns Limit
    • @return integer
      */
      public function getLimit()
      Unknown macro: { return $this->_itemPerPage; }

    }

    /**

    • Zend_Paginator_Strategy_Abstract
    • @package Zend_Paginator
      */
      abstract class Zend_Paginator_Strategy_Abstract {

    /**

    • Returns Pagination
    • @param integer $pageCount
    • @param integer $pageOn
    • @return Zend_Paginator_Item_Abstract[]
      */
      abstract public function paginate($pageCount, $pageOn);

    }

    /**

    • Zend_Paginator_Strategy_Simple
    • @package Zend_Paginator
      */
      class Zend_Paginator_Strategy_Simple extends Zend_Paginator_Strategy_Abstract {

    /**

    • Returns Pagination
    • @param integer $pageCount
    • @param integer $pageOn
    • @return Zend_Paginator_Item_Abstract[]
      */
      public function paginate($pageCount, $pageOn)
      Unknown macro: { $result = array(); for ($i = 1; $i <= $pageCount; $i++)
      Unknown macro: { $result[] = new Zend_Paginator_Item_Page($i, ($i == $pageOn)); }
      return $result; }

    }

    /**

    • Zend_Paginator_Item_Abstract
      *
    • @package Zend_Paginator
      */
      abstract class Zend_Paginator_Item_Abstract {
      }

    /**

    • Zend_Paginator_Item_Page
    • @package Zend_Paginator
      */
      class Zend_Paginator_Item_Page extends Zend_Paginator_Item_Abstract {

    /**

    • Page Number
    • @var int
      */
      private $_number;

    /**

    • Page Activity
    • @var boolean
      */
      private $_active;

    /**

    • Constructs Object
    • @param int $number
    • @param boolean $active
      */
      public function __construct($number, $active = false)
      Unknown macro: { $this->_number = $number; $this->_active = $active; }

    /**

    • Returns Page Number
    • @return int
      */
      public function getNumber()
      Unknown macro: { return $this->_number; }

    /**

    • Checks if Page is Active
    • @return boolean
      */
      public function isActive()
      Unknown macro: { return $this->_active; }

    }

    /**

    • Zend_Paginator_Item_Separator
    • @package Zend_Paginator
      */
      class Zend_Paginator_Item_Separator extends Zend_Paginator_Item_Abstract {
      }

    /**

    • Usage
      */

    $paginator = new Zend_Paginator(new Zend_Paginator_Strategy_simple(), 100, 10, 1);
    $filter = new MyFilter();
    $result = $filter->getItemArray(
    $paginator->getOffset(),
    $paginator->limit()
    );
    $pages = $paginator->paginate();

    ]]></ac:plain-text-body></ac:macro>

  4. Jun 11, 2008

    <p>Looks good, but please, use HTML lists as the default presentetion - you know, promoting best practices.</p>

  5. Jun 12, 2008

    <p>I'm quite confused about <code>Zend_Paginator_Adapter_DbSelect</code>. You stated that <cite>currently only Zend_Db_Table_Abstract classes are supported</cite> but I don't see Db_Table used anywhere.</p>

    <p>Actually COUNT query is executed on reseted clone of select object:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $expression = new Zend_Db_Expr('COUNT AS ' . self::ROW_COUNT_COLUMN);
    $rowCountSelect->reset()>from($this>_select, $expression);
    $result = $rowCountSelect->query()->fetch();
    ]]></ac:plain-text-body></ac:macro>

    <p>But consider this example</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $select->where("column2=?", "foo");
    $paginator = new Zend_Paginator_Adapter_DbSelect($select);
    ]]></ac:plain-text-body></ac:macro>

    <p>Since <em>count-select</em> is reseted, it will miss <code>where</code> condition and will count all rows, not only those wanted. Not mentioning more complicated selects with joins and group by parts. Shouldn't only selected columns be changed? I believe this can be done with <code>Db_Select</code> in trunk and reseting <code>COLUMNS</code> part and with <code>columns()</code> method.</p>

    <p>Also would be nice if <em>count-select</em> could be alternatively passed to <code>Zend_Paginator_Adapter_DbSelect</code> along with select. This select can be more optimized for count (e.g. omitted unnecessary joins, query another table where row counts are precounted, ...).</p>

    1. Jun 13, 2008

      <p>The part about Db_Table support was a remainder of the old paginate proposal. Somehow we overlooked it. Thanks for mentioning it! I've removed the reference to Db_Table and clarified a bit on how the Db adapter works.</p>

  6. Jun 13, 2008

    <p>As discussed in IRC:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    <DASPRiD> norm2782, and well, for mysql you could make the allmighty solution
    <DASPRiD> norm2782, put a SQL_CALC_FOUND_ROWS after the SELECT
    <norm2782> if (mysql)

    Unknown macro: { $sql->useDaspridsHack() }

    <DASPRiD> :>
    <DASPRiD> it's no hack
    <DASPRiD> wait
    <DASPRiD> norm2782, http://home.dasprids.de/Mysql.phps
    <DASPRiD> this works on very huge tables
    <norm2782> DASPRiD: thanks ^^
    <DASPRiD> norm2782, my plesure
    <DASPRiD> norm2782, sadly a mysql-only feature, but works very well
    <DASPRiD> norm2782, and as i think, that most/many zf developers use mysql, this could be a huge advantage of zend_paginator
    <norm2782> DASPRiD: good point
    <norm2782> DASPRiD: this works on all mysql versions and all queries?
    <DASPRiD> yes
    <DASPRiD> norm2782, by the way, i have a suggestion for zend_paginator
    <norm2782> DASPRiD: there seems to be a catch using that trick and Unions
    <DASPRiD> make a setMaxPages() method, so you can limit how many entries can be shown in total
    <norm2782> DASPRiD: tell me ^^
    <norm2782> DASPRiD: usecase?
    <DASPRiD> say there are 500 rows, but you only want to show 100 (10 per page), then you limit it to 10 pages
    <DASPRiD> norm2782, usecase is performance again, see:
    <DASPRiD> when you do a LIMIT 100000,10
    <DASPRiD> mysql has to select the 100000 rows before as well
    <DASPRiD> to get those 10 pages
    <norm2782> DASPRiD: hmm ok, but couldn't you just do that at the query itself with LIMIT?
    <DASPRiD> norm2782, usually, yes, but that wouldn't work with the SQL_CALC_FOUND_ROWS trick, as it ignores LIMIT
    <norm2782> DASPRiD: hmm.. in that case SQL_CALC_FOUND_ROWS is not universal =/
    <norm2782> DASPRiD: and limiting at PHP level wouldn't really help performance
    <DASPRiD> norm2782, it surely should
    <DASPRiD> else some user says "page/10000" in the url
    <DASPRiD> and nothing would check it
    <DASPRiD> it should be limited by php in that case
    <DASPRiD> so it decreases the page number to the limit then
    <norm2782> DASPRiD: could you perhaps post that at the proposal ?
    <DASPRiD> norm2782, also, your method is in that case universal as well
    <DASPRiD> if i do a query with LIMIT 10, 100
    <DASPRiD> zend_paginate would return rows form 0 to 90
    <DASPRiD> because it doesn't care about the limit clause yet
    <DASPRiD> norm2782, i'm currently lazy, may i just paste our chatlog?
    <norm2782> DASPRiD: heh, sure
    <norm2782> as long as it doesn't make me look like a dikdik
    ]]></ac:plain-text-body></ac:macro>

    1. Jun 13, 2008

      <p>Hi Ben,</p>

      <p>We are providing a solution for user-supplied counts in the DbSelect adapter, but that (like the rest of the adapter) relies heavily on Zend_Db_Select. Zend_Paginator will not contain any database-specific hacks, tricks, workarounds, tune-ups, or special cases of any kind. <ac:emoticon ac:name="wink" /></p>

      <p>p.s. norm2782 is Jurriën, in case anyone was confused.</p>

  7. Jun 15, 2008

    fc

    <p>I've seen dozens of different paginators in the last couple of years, and to be honest, this one is by far the best. You got everything right! I can't wait for this component to hit the incubator.</p>

    <p>It's great, well done guys <ac:emoticon ac:name="wink" /> </p>

    1. Jun 16, 2008

      <p>Thanks, Federico! <ac:emoticon ac:name="smile" /></p>

  8. Jun 15, 2008

    <p>I tried to find out how well SeekableIterator's are supported, but as it's not mentioned in the proposal I had to read the current code.</p>

    <p>Zend_Mail_Storage classes are one example that support SeekableIterator to avoid fetching not needed messages (or at least their headers). I guess performance is also a reason why other Iterators will implement SeekableIterator.</p>

    <p>But with the current implementation you'd iterate twice, the first time just to get $currentPageItemCount. That should be addressed rather sooner than later to make a good library class. For me it would always be the same as getItemCountPerPage(), except on the last page, where it's a simple modulo operation. But maybe I miss something and if it would be a better idea to be able to help the paginator with a method in the iterator, via a TBD interface.</p>

    1. Jun 16, 2008

      <p>Hi Nico,</p>

      <p>There are a few little performance optimizations I am planning on implementing once the unit tests are complete. My coding process is 1) get the functionality working as desired with as little distraction as possible, 2) write unit tests, then 3) go back and iteratively optimize while running the unit tests.</p>

      <p>If you have any more suggestions, though, please post them. Thanks!</p>

  9. Jun 16, 2008

    <p>Does the method setCountSelect() support MySQL for the following statements?</p>

    <p>SELECT SQL_CALC_FOUND_ROWS * FROM mytable;</p>

    <p>$customSelectQuery = "SELECT FOUND_ROWS()";</p>

    <p>$paginator->setCountSelect($customSelectQuery);</p>

    <p>This is assuming you subclass Zend_Db_Table_Select and add a method for SQL_CALC_FOUND_ROWS.</p>

    1. Jun 16, 2008

      <p><a class="external-link" href="http://framework.zend.com/wiki/pages/viewpage.action?pageId=43560&focusedCommentId=3866818#comment-3866818">http://framework.zend.com/wiki/pages/viewpage.action?pageId=43560&amp;focusedCommentId=3866818#comment-3866818</a></p>

  10. Jun 17, 2008

    <p>could you add SQL_CALC_FOUND_ROWS support only for MySQL adapter? it's really better than count<ac:emoticon ac:name="yellow-star" />.<br />
    it's official mysql statement, it's faster with large tables, it can be used with any group functions in complex mysql query, e.g. SELECT SUM(...), COUNT(...), AVG() FROM ...<br />
    it will return rows with limit rules! it just calculates total rows regardless of limit params.<br />
    it's good replacement for SELECT COUNT ... FROM (SELECT ...))</p>

    <p>Thank you.</p>

  11. Jun 18, 2008

    <p>I would really like to use this component, but the dependency on the Zend MVC and routing system is a deal breaker for me. (Zend_Controller_Front, the router, etc. should be listed as required dependencies, since I could not tell from the code how they're optional. Please feel free to clarify as needed.) I use my own MVC system, so I don't want this automatically creating URLs for the ZF router.</p>

    <p>In my opinion, all URL/parameter/routing-related functionality should be completely removed from the main Paginator classes. I feel that the Paginator should not try to encapsulate the rendering of UI (such as the page navigation controls), since it reduces cohesion as the class takes on non-paging functionality. It would be better to provide a concise API to access the "page state" (the current page, page range, page size, etc). Then provide a default view helper which takes the paginator object and a partial, and returns a page navigation UI. The paginator should serve only as a data model into the paged data collection.</p>

    <p>I think the focus of the Zend_Paginator_Adapter_* classes should be changed. There should be an interface defined that the Paginator component uses to retrieve data. This way, arbitrary classes can be data providers for pagination by implementing the interface. You can provide default implementations for arrays, Db_Select objects, etc. if so inclined. But for real applications, the onus should be on the developer to implement this interface on data models that (s)he wants to be pagable. This cleanly side-steps the whole SQL_CALC_FOUND_ROWS issue.</p>

    <p>With the adapter classes modified in this way, most of the logic in the Zend_Paginator_Adapter_Abstract class could be moved into the core Zend_Paginator class, and the factory method could be removed.</p>

    <p>In light of my above comments, I don't understand the purpose of the Page and PageSet classes. If the "page state" is managed by Zend_Paginator, and knowledge of the ZF MVC is factored out (so you don't have URLs associated to page numbers in the Paginator), then the Page class is not left with much to do. The PageSet class, IMHO, seems like an overall detriment to cohesion. As stated above, view rendering functionality should be moved to a separate view helper. Most of the state maintained by PageSet appears to be redundant with the main Paginator class. I think the component could be implemented without these classes, but feel free to clarify the case for their existence.</p>

    <p>I think Zend_Paginator_Adapter_Abstract::setConfig() should take an array, as well as a Zend_Config object, to be consistent with the majority of ZF.</p>

    <p>The ScrollingStyle classes are good strategies for different page navigation behaviours. However, as such, they should not be used outside that context. That is, the only code that should have a dependency on that interface should be the view helper that renders the page navigation controls.</p>

    <p>I realize that a lot of what I've said is rather abstract. If these ideas sound appealing to you (and I hope they do, since I don't feel like writing my own version of this from scratch... (: ), then I would be happy to provide some class skeletons to make this more concrete.</p>

    1. Jun 18, 2008

      <blockquote>
      <p>I would really like to use this component, but the dependency on the Zend MVC and routing system is a deal breaker for me. (Zend_Controller_Front, the router, etc. should be listed as required dependencies, since I could not tell from the code how they're optional. Please feel free to clarify as needed.) I use my own MVC system, so I don't want this automatically creating URLs for the ZF router.</p>

      <p>In my opinion, all URL/parameter/routing-related functionality should be completely removed from the main Paginator classes.</p></blockquote>

      <p>URL generation is optional. You can call setRouteName() and setRouteOptions() or choose not to. If you call them, Page::getUrl() returns the assembled URL, otherwise it does not.</p>

      <blockquote>
      <p>I feel that the Paginator should not try to encapsulate the rendering of UI (such as the page navigation controls), since it reduces cohesion as the class takes on non-paging functionality. It would be better to provide a concise API to access the "page state" (the current page, page range, page size, etc). Then provide a default view helper which takes the paginator object and a partial, and returns a page navigation UI. The paginator should serve only as a data model into the paged data collection.</p>

      <p>I think the focus of the Zend_Paginator_Adapter_* classes should be changed. There should be an interface defined that the Paginator component uses to retrieve data. This way, arbitrary classes can be data providers for pagination by implementing the interface. You can provide default implementations for arrays, Db_Select objects, etc. if so inclined. But for real applications, the onus should be on the developer to implement this interface on data models that (s)he wants to be pagable. This cleanly side-steps the whole SQL_CALC_FOUND_ROWS issue.</p></blockquote>

      <p>Bryce, I may be missing something, but I believe you just described exactly how Zend_Paginator works (perhaps better than we did). To take your points in order:</p>

      <ul class="alternate">
      <li>"Concise API to access the 'page state'": This is Zend_Paginator_Page.</li>
      <li>"View helper which ... returns a page naviation UI": This is Zend_View_Helper_PaginationControl.</li>
      <li>"Focus of Zend_Paginator_Adapter_*": The focus is exactly as you describe. There's an interface should people choose to implement their own adapters, and they can instantiate them directly and interact with the rest of the component normally.</li>
      </ul>

      <p>I believe part of your confusion is from the <strong>multiple</strong> ways of rendering pagination control UI. These are the different ways you can do it, from most magical to least. If you've set all the defaults you can do this:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      // Controller
      $this->view->pages = $pages; // PageSet

      // View
      <?= $this->pages; ?>
      ]]></ac:plain-text-body></ac:macro>

      <p>Or you can do this:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      // Controller
      $this->view->pages = $pages; // PageSet

      // View
      <?= $this->paginationControl($this->pages); ?>
      ]]></ac:plain-text-body></ac:macro>

      <p>Or this:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      // Controller
      $this->view->pages = $pages; // PageSet

      // View
      <?= $this->paginationControl($this->pages, 'my_pagination_control.phtml'); ?>
      ]]></ac:plain-text-body></ac:macro>

      <p>Which is another way of saying this:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      // Controller
      $this->view->pages = $pages; // PageSet

      // View
      <?= $this->partial('my_pagination_control.phtml', $this->pages); ?>
      ]]></ac:plain-text-body></ac:macro>

      <blockquote>
      <p>I think Zend_Paginator_Adapter_Abstract::setConfig() should take an array, as well as a Zend_Config object, to be consistent with the majority of ZF.</p></blockquote>

      <p>That's reasonable.</p>

      <blockquote>
      <p>The ScrollingStyle classes are good strategies for different page navigation behaviours. However, as such, they should not be used outside that context. That is, the only code that should have a dependency on that interface should be the view helper that renders the page navigation controls.</p></blockquote>

      <p>This is a valid opinion, but I'm not sure it's necessarily any more correct than the way the code is currently organized. Alternative APIs derived from that philosophy would be more complicated unless you had a variety of pagination control helpers (e.g., SlidingPaginationControl). I think that's less flexible in the end.</p>

      <p>Thanks for taking the time to write up such a detailed comment. I think there was some confusion about how the code works, and on some areas our philosophies differ a bit, but your suggestion about configuration arrays is a good one and we'll make that change.</p>

      1. Jun 20, 2008

        <blockquote>
        <p>I think the focus of the Zend_Paginator_Adapter_* classes should be changed. There should be an interface defined that the Paginator component uses to retrieve data. This way, arbitrary classes can be data providers for pagination by implementing the interface.<br />
        You can provide default implementations for arrays, Db_Select objects, etc. if so inclined. But for real applications, the onus should be on the developer to implement this interface on data models that (s)he wants to be pagable. This cleanly side-steps the whole SQL_CALC_FOUND_ROWS issue.</p></blockquote>

        <blockquote>
        <p>"Focus of Zend_Paginator_Adapter_*": The focus is exactly as you describe. There's an interface should people choose to implement their own adapters, and they can instantiate them directly and interact with the rest of the component normally.</p></blockquote>

        <p>I think was he meant, was that the data provider would implement the interface. So the Adapter is not a separate class in Zend_Paginator, but i.e. implemented in a Zend_Db class like Zend_Db_Adapter_Mysqli and you won't need a separate Zend_Paginator_Adapter class to support SQL_CALC_FOUND_ROWS. Just the data provider become pagination-aware. Or course the disadvantage would be the coupling.</p>

      2. Jun 22, 2008

        <p>My description was pretty abstract, so if you think that it matches the current implementation, then at least we both agree on intention. <ac:emoticon ac:name="smile" /> And, I should admit that I didn't study every line of the code in great detail, so it's certainly possible that I'm misunderstanding part (or the whole) of it.</p>

        <p>What I was describing would imply a usage like the following:</p>
        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        <?php
        // Make my model class a data provider for Zend_Paginator
        class Models_Blog_Articles extends Models_Base implements Zend_Paginator_Adapter_Interface {
        ...
        public function getCount()

        Unknown macro: { ... }


        public function getItems($offset, $limit)

        public function hasItemsAt($offset)

        Unknown macro: { ... }

        }
        ?>

        <?php
        // Controller
        $data = new Models_Blog_Articles;
        $paginator = new Zend_Paginator($data);
        $paginator->setConfig(Zend_Registry::get('configuration'), 'pagination')
        >setCurrentPageNumber($this>_getParam('page'));
        $this->view->paginator = $paginator; // Zend_Paginator object
        ?>

        <!-- View script -->
        <?php if (count($this->paginator)): ?>
        <ul>
        <?php
        // Zend_Paginator implements IteratorAggregate so iteration logic is factored out
        foreach ($this->paginator as $item): ?>
        <li><?= $item; ?></li>
        <?php endforeach; ?>
        </ul>
        <?php endif; ?>

        <?= $this->paginationControl(new Zend_Paginator_ScrollingStyle_Sliding($this->paginator)); ?>

        <!-- Page nav control -->
        <?php if ($this->numPages): ?>
        <div id="paginationControl">
        <!-- Previous page link -->
        <?php if ($this->previousPage): ?>
        <a href="<?= $this->url(array('page'=>$this->previousPage)); ?>">< Previous</a> |
        <?php else: ?>
        <span class="disabled">< Previous</span> |
        <?php endif; ?>

        <!-- Numbered page links -->
        <?php for ($page = $this->lowPage; $page <= $this->highPage; ++$page): ?>
        <?php if ($page != $this->currentPage): ?>
        <a href="<?= $this->url(array('page'=>$page)); ?>"><?= $page; ?></a> |
        <?php else: ?>
        <?= $page; ?> |
        <?php endif; ?>
        <?php endforeach; ?>
        ]]></ac:plain-text-body></ac:macro>

        <p>Here are the key differences:</p>
        <ul>
        <li>The pagable data can directly implement the adatper interface. This allows arbitrary data model classes to become pagable, without requiring a separate concreate adapter implementation for every model class. Of course, there's no reason why you couldn't ship default implementations for DbSelect, Array, etc.</li>
        <li>No factory. The Paginator takes advantage of polymorphism to eliminate the factory and the conditional block it used to determine which adapter class to instantiate. Obviously, this increases flexibility and maintainability.</li>
        <li>No PageSet or Page classes. The Paginator implements IteratorAggregate and Countable. The view script that displays the paged items can use the paginator object just like an array. Behind the scenes, the Paginator delegates to the data adapter via its interface. This makes for a fairly clean API, and it would be easier to reuse the view script in other contexts, if so desired. Also, this means that the component doesn't try to encapsulate the process of rendering the page nav view, since there's no render() or __toString(). I think it's much more transparent and cohesive to for the view script to directly call the view helper.</li>
        <li>The ScrollingStyle classes become the data model for the page nav control. They would effectively decorate the Paginator with different page range behaviours, and provide the public properties needed by the page nav partial to render. This completely removes the dependency on ScrollingStyle classes from the Paginator, which increases cohesion in the Paginator.</li>
        <li>Use of the url() view helper instead of the Page::getUrl() method. This provides a lot more flexibility to the application developer on how they want to create the urls, as well as removes the Paginator's dependency on the ZF router, front controller, etc. Since no router-related configuration needs to be made to the Paginator, it simplifies usage. At the same time, cohesion and maintainability are increased.</li>
        </ul>

        <p>As you eluded to, much of this may simply come down to philosophical differences, in which case take what you like, and disregard the rest. I just wanted to clarify my statements with something a little more concrete.</p>

        1. Jun 22, 2008

          <p>Hmm... well, you've got some good points. This would delay the inclusion of the component, but it's better to do it right the first time.</p>

        2. Jun 22, 2008

          <p>All of the methods in Zend_Paginator_Adapter_Interface can be optional. getCount() is the same as implementing the Countable interface, hasItemsAt() (or better hasItemAt()) is the same as offsetExists() in ArrayAccess and getItems() can be replaced by a LimitIterator. If these interfaces are also supported we can support non ZF classes and also avoid making other ZF components depend in Zend_Paginator. Of course not every data provider supports cursors or it's not always good to use them, so implementing getItems($limit, $offset) might be better than.</p>

          1. Jun 23, 2008

            <p>Actually, the very same thought had crossed my mind. I was (and still am) unsure whether I wanted to force data provider classes to implement all of ArrayAccess and Countable for paging purposes. Maybe they want to do something else; I haven't thought this completely through yet...</p>

            <p>LimitIterator would be a bit more problematic since your model would have to extend LimitIterator, which could complicate your class hierarchy. Having some way to pass $limit and $offset to the model is critical, however, for efficiently getting a page of data from the database, and must be supported.</p>

            1. Jul 03, 2008

              <p>You wouldn't implement LimitIterator. You implement SeekableIterator to support LimitIterator. A couple of models fetch one item at a time and can seek to a specific item, so they don't need to know the range you want to access. That's why you just need to fetch via a LimitIterator if your model already implements SeekableIerator, instead of a new method.</p>

        3. Jun 23, 2008

          <p>"The pagable data can directly implement the adatper interface. This allows arbitrary data model classes to become pagable, without requiring a separate concreate adapter implementation for every model class." - Pagination mostly used with some sort of data queries with aggregation, grouping, sorting , not with simple one model queries.</p>

          <p>"No PageSet or Page classes." - I belevie that Page, Separator (new one), (maybe PageSet?) classes is necessary to provide such style pagination: 1 2 3 ... 5 ... 10 = (page) (page) (page) (separator) (page) (separator) (page). So in the template is posible to render this type of pagination. </p>

          1. Jun 23, 2008

            <blockquote>
            <p>"The pagable data can directly implement the adatper interface. This allows arbitrary data model classes to become pagable, without requiring a separate concreate adapter implementation for every model class." - Pagination mostly used with some sort of data queries with aggregation, grouping, sorting , not with simple one model queries.</p></blockquote>

            <p>In the interfaces I described, I don't see any place they impose any limitaion on query complexity beyond that of the database itself. If you point out specific problem areas, I'm sure we can resolve those.</p>

            <blockquote>
            <p>"No PageSet or Page classes." - I belevie that Page, Separator (new one), (maybe PageSet?) classes is necessary to provide such style pagination: 1 2 3 ... 5 ... 10 = (page) (page) (page) (separator) (page) (separator) (page). So in the template is posible to render this type of pagination.</p></blockquote>

            <p>To me, this sounds like either a presentational or a ScrollingStyle logic issue; as such, I would solve that with an appropriate view helper or ScrollingStyle implementation. Assuming the Paginator class provided all the basic paging info needed, it should be pretty easy to display whatever separators in whatever order you wanted.</p>

        4. Jun 23, 2008

          <p>Hi Bryce,</p>

          <p>I encourage you to check out the latest version in trunk.</p>

          <blockquote><p>- The pagable data can directly implement the adapter interface. This allows arbitrary data model classes to become pagable, without requiring a separate concreate adapter implementation for every model class. Of course, there's no reason why you couldn't ship default implementations for DbSelect, Array, etc.</p></blockquote>

          <p>This was a smart idea. I've implemented it such that Zend_Paginator_Adapter_Interface extends Countable and requires only two methods: count() and getItems(). In many cases (where count() is already implemented) users will only have to implement getItems(). </p>

          <blockquote><p>- No factory. The Paginator takes advantage of polymorphism to eliminate the factory and the conditional block it used to determine which adapter class to instantiate. Obviously, this increases flexibility and maintainability. </p></blockquote>

          <p>I initially removed the factory, but put it back at Jurrien's suggestion. It now uses the built-in implementations, but is by no means necessary to use the component. (To be fair, it never was.) </p>

          <blockquote>
          <ul class="alternate">
          <li>No PageSet or Page classes. The Paginator implements IteratorAggregate and Countable. The view script that displays the paged items can use the paginator object just like an array. Behind the scenes, the Paginator delegates to the data adapter via its interface. This makes for a fairly clean API, and it would be easier to reuse the view script in other contexts, if so desired. Also, this means that the component doesn't try to encapsulate the process of rendering the page nav view, since there's no render() or __toString(). I think it's much more transparent and cohesive to for the view script to directly call the view helper.</li>
          </ul>

          <ul class="alternate">
          <li>The ScrollingStyle classes become the data model for the page nav control. They would effectively decorate the Paginator with different page range behaviours, and provide the public properties needed by the page nav partial to render. This completely removes the dependency on ScrollingStyle classes from the Paginator, which increases cohesion in the Paginator.</li>
          </ul>

          <ul class="alternate">
          <li>Use of the url() view helper instead of the Page::getUrl() method. This provides a lot more flexibility to the application developer on how they want to create the urls, as well as removes the Paginator's dependency on the ZF router, front controller, etc. Since no router-related configuration needs to be made to the Paginator, it simplifies usage. At the same time, cohesion and maintainability are increased.</li>
          </ul>
          </blockquote>

          <p>Nice going! All of this was done pretty much exactly as you described.</p>

          <p>Thanks for your input--as well as Nico's suggestion to look to the SPL for some of this functionality.</p>

          1. Jun 23, 2008

            <p>I'm really happy I could have some constructive input! Thank you!</p>

            <p>I looked at r76 and collected my thoughts below. As before, take what you will.</p>

            <p><strong>ScrollingStyle's relationship to Paginator</strong><br />
            There are several things going on that relate specifically to the ScrollingStyle classes inside the Paginator class. To me, it seems that most of that can be factored out to other places.</p>

            <p>The page range and the pages in the range seem dependent on the ScrollingStyle logic. Therefore, these should become configureable properties of ScrollingStyle classes, rather than the Paginator. The ScrollingStyle interface already includes a getPages() method, which should (IMHO) encapsulate all that logic which is currently divided between Paginator::getPages(), Paginator::_createPages() and ScrollingStyle::getPages(). Introducing a ScrollingStyle abstract class, in addition to the interface, may potentially be helpful.</p>

            <p>The PaginatorControl view helper ultimately depends on the ScrollingStyle to determine what it page choices and navigation controls it displays. As such, it should be directly passed a ScrollingStyle implementation. Right now, it gets an anonymous stdClass object with all the relevant state. This state is really ScrollingStyle information, and could be exposed as public properties or methods, and thus gain a formal API. The ScrollingStyle interface already has a dependency on the Paginator, so it can safely call back into the Paginator for any data it needs.</p>

            <p>There are several methods in the Paginator to configure a PluginLoader object so it can find ScrollingStyle objects. Since it's the PaginatorControl that really needs ScrollingStyle objects, these could be moved to there, and function much like its already-present configuration for default view partial. Further, since these seem to essentially proxy to the PluginLoader, we could just provide an optional method that allows the developer to set a pre-configured PluginLoader instance. If a user PluginLoader is never set, then the PaginatorControl can create a private one with the default configuration. This would reduce the class-loading configuration footprint down to one method.</p>

            <p><strong>Current Page Items</strong><br />
            In my opinion, the Paginator should be driven based on its notion of what the current page is. There's already a setCurrentPageNumber() method to set that piece of state. From that perspective, getCurrentItems(), getItemsByPage(), and getIterator() are redundant with each other. We could pare it down to one method (preferably getIterator()), which would return the items from the page set by setCurrentPageNumber(). I expect that the most common, standard use case would be to set the current page (from a request parameter) and then just start iterating on the Paginator, assuming that it will just return the current page's items. However, any given page could be retrieved just by re-assigning the current page number.</p>

            <p><strong>Item counts</strong><br />
            Right now, there are methods count() for getting the total page count, and getCurrentItemCount() for getting the count of items on the current page. I think it would be more intuitive for count() to return the number of items on the page, and have a getPageCount() method to find out how many pages there are. This would keep the all the iteration-focused interfaces consistenly talking about the page items. (For example, you could loop through the page items with "for ($i=1; $i<count($paginator); ++$i)" if you wanted.)</p>

            <p>Also, getItemCount() shouldn't need to manually count LimitIterator objects: we can simply ensure all objects have a count() method or are an array. If an object, such as a LimitIterator, needs to be used, it can be decorated or subclassed with something that implements Countable.</p>

            <p><strong>Retrieving page items</strong><br />
            Zend_Paginator_Adapter_Interface::getItems() is currently defined as taking a page number and a page item count. I think rather it should be getItems($offset, $limit), because the notion of "pages" should be completely encapsulated by the Paginator. Otherwise, implementors of this interface have to continually convert page/count into limit/offset. I know Zend_Db_Select can directly consume page/count with its limitPage(), but that is the exception rather than the rule. If you use LimitIterator, or if (like me) you prefer to hand-code your SQL, it's more useful to be given the correct offset/limit pair than page/count.</p>

            <p><strong>Factory</strong><br />
            Lastly, unless you feel stronly about it, I encourage you to re-consider removing the factory. Such a factory is better off implemented in user-land code, where it's more in a position to determine the correct type of object the application needs. Framework-level code should just ensure it provides the flexibility necessary for this (which is done now through the polymorphic data adapter interface). As it stands, the factory does little beyond try to automatically select a data adapter with some type guessing. If the use of this factory were encouraged, I feel it would do a disservice to developers by attempting to sheild them from thinking about how the data fits together. Additionally, it could become a maintenance headache as users request that the factory support more varied kinds of objects.</p>

            <p><strong>Fin</strong><br />
            This is actually quite constructive. Even if I sat down to design this component all by myself, I would not have come to a design as strong as the one we're talking about right now. You already had some pretty good ideas in there that I probably wouldn't have thought of.</p>

            1. Jun 24, 2008

              <p>Hi Bryce,</p>

              <p>Thank you for your comments! They have really helped the Paginator to become a better component.<br />
              I'll look through your latest list of suggestions in more detail tonight, but I would like to comment on two of 'em now:</p>

              <p>Item counts:<br />
              Agreed <ac:emoticon ac:name="smile" /></p>

              <p>Factory:<br />
              While I do see your point and partially agree with it, I don't see the need for the (potential) repetition caused by removing the factory. It's a non-intrusive method for convenience only. I'm lazy, so I want to have my Paginator instance with as little thinking and as little typing as possible. The factory would only support the adapters included in the Paginator component. Any other adapters can be used the "manual" way. In fact, all adapters can be used without the factory. Again... it's a lazy-man's feature. I don't see any harm in keeping it.</p>

            2. Jun 26, 2008

              <p>Hi Bryce,</p>

              <p><strong>ScrollingStyle's relationship to Paginator</strong><br />
              If I recall correctly Matthew is looking in to this at the moment. We're going to separate the ScrollingStyles and the Paginator as much as possible (as long as it makes sense <ac:emoticon ac:name="wink" />).</p>

              <p><strong>Current Page Items</strong><br />
              I'd like to keep getItemsByPage(). This would give developers some more control over which page they want to fetch. The getIterator() is a proxy to getCurrentItems() at the moment. While I agree with getCurrentItems() being redundant, it does descibe the result of the method better than getIterator(). But that's the only argument I have in favor of keeping it. I'll discuss it with Matthew.</p>

              <p><strong>Item counts</strong><br />
              I looked into this a bit and I'm actually not really sure if this is such a good idea after all. Basically you have only two possibilities for the result of the count($paginator) call:</p>
              <ul class="alternate">
              <li>count returns less than itemsPerPage (possible when there's just one page, or on the last page)</li>
              <li>count returns itemsPerPage.</li>
              </ul>

              <p>Basically these two would do the same (assuming you'd want the $i):</p>
              <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
              $i = 0;
              foreach($paginator as $item) {
              // Output item
              $i++;
              }
              ]]></ac:plain-text-body></ac:macro>
              <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
              for ($i = 0; $i <= $paginator->getCurrentItemCount(); $i++) {
              // Output item
              }
              ]]></ac:plain-text-body></ac:macro>
              <p>The current count implementation is used internally and could be used in case of the drop-down list with all pages (as shown in the demo).</p>

              <p><strong>Retrieving page items</strong><br />
              Done <ac:emoticon ac:name="smile" /></p>

              <p><strong>Fin</strong><br />
              I'd like to thank you again for your feedback. Hopefully with these modifications in place we can wrap the development of this component up and have it ready for 1.6.1 or 1.7.</p>

              1. Jun 26, 2008

                <p><strong>ScrollingStyle's relationship to Paginator</strong><br />
                I look forward to seeing what you guys come up with! If you think it would be helpful, I can send some class skeletons to illustrate what I meant more clearly.</p>

                <p><strong>Current Page Items</strong><br />
                <strong>Item Counts</strong><br />
                These are fairly small issues overall, so I'm not going to argue against what you have here. I already said my piece... <ac:emoticon ac:name="smile" /></p>

                <p><strong>Retrieving page items</strong><br />
                Thank you! This is one I really wanted. <ac:emoticon ac:name="smile" /></p>

                <p>Thank you guys for the effort you've put into this!</p>

                1. Jun 29, 2008

                  <p>I'd very much like to have a look at those class skeletons. Hopefully they'll spark some ideas <ac:emoticon ac:name="smile" /></p>

  12. Jun 20, 2008

    <p>Great work on the Zend_Paginator thus far =)</p>

    <p>I have installed it into my application to try it out and I ran into a few problems. Keep in mind that i have about 2weeks zendframework/php experience.</p>

    <p>I put the following code into my controller:</p>
    <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
    <?php
    ...
    $paginator = Zend_Paginator::factory($list->selectAll());
    $paginator->setScrollingStyle('Sliding')
    ->setItemCountPerPage(3)
    ->setPageRange(10)
    >setCurrentPageNumber($this>_request->getParam('page'));
    $this->view->pages = $paginator->getPages();
    ]]></ac:plain-text-body></ac:macro>

    <p>This gives an error on the last line in the getPages() function</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    Fatal error: Call to undefined method Zend_Controller_Router_Rewrite::assemble() ... on line 229
    ]]></ac:plain-text-body></ac:macro>

    <p>if we go to line 229 of PageSet.php we find the following function</p>
    <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
    /**

    • Assigns page URLs if a route name has been set. This is optional
    • in case routes are not necessary (for example, in a console
    • application) or are generated elsewhere (e.g., JavaScript).
      */
      protected function _assignPageUrls()
      {
      // Get everything related to the route
      require_once 'Zend/Controller/Front.php';
      $router = Zend_Controller_Front::getInstance()->getRouter();
      $routeName = $this->_adapter->getRouteName();
      $routeParams = $this->_adapter->getRouteParams();
      $routeOptions = $this->_adapter->getRouteOptions();

    // Loop through and assign URLs
    for ($pageNumber = 1; $pageNumber <= $this->pageCount; $pageNumber++)

    Unknown macro: { $routeParams[$this->_adapter->getPageNumberParam()] = $pageNumber; $url = $router->assemble($routeParams, $routeName, $routeOptions['reset'], $routeOptions['encode']); // <---- LINE 229 $this->allPages[$pageNumber]->setUrl($url); }

    }
    ]]></ac:plain-text-body></ac:macro>

    <p>I have had several problems with this function:</p>
    <ol>
    <li>There seems to be a typo on line 229 because the $router object has no assemble function</li>
    <li>This function does not get the current route, instead it seems to always use "default"</li>
    </ol>

    <p>In order to get it working I had to make the following changes:</p>
    <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
    /**

    • Assigns page URLs if a route name has been set. This is optional
    • in case routes are not necessary (for example, in a console
    • application) or are generated elsewhere (e.g., JavaScript).
      */
      protected function _assignPageUrls()
      {
      // Get everything related to the route
      require_once 'Zend/Controller/Front.php';
      $router = Zend_Controller_Front::getInstance()->getRouter();
      $routeName = $router->getCurrentRouteName();
      $route = $router->getCurrentRoute();
      //$routeName = $this->_adapter->getRouteName();
      //$routeParams = $this->_adapter->getRouteParams();
      //$routeOptions = $this->_adapter->getRouteOptions();

    // Loop through and assign URLs
    for ($pageNumber = 1; $pageNumber <= $this->pageCount; $pageNumber++)

    Unknown macro: { $routeParams[$this->_adapter->getPageNumberParam()] = $pageNumber; //$url = $router->assemble($routeParams, $routeName, $routeOptions['reset'], $routeOptions['encode']); $url = $route->assemble($routeParams, $routeName, $routeOptions['reset'], $routeOptions['encode']); $this->allPages[$pageNumber]->setUrl($url); }

    }
    ]]></ac:plain-text-body></ac:macro>

    <p>There is still one more issue. The page urls do not get created correctly. The paginator does not seem to handle the following 2 routes very well because it does not seem to have a way of handling a url without a reference to a page number. </p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

    1. Example: http://localhost/page2
      routes.allpaged.type = "Zend_Controller_Router_Route_Regex"
      routes.allpaged.route = "page(\d+)"
      routes.allpaged.defaults.controller = "index"
      routes.allpaged.defaults.action = "index"
      routes.allpaged.map.1 = "page"
      routes.allpaged.reverse = "page%d"
    1. Example: http://localhost/
      routes.all.type = "Zend_Controller_Router_Route_Regex"
      routes.all.route = ""
      routes.all.defaults.controller = "index"
      routes.all.defaults.action = "index"
      routes.all.defaults.page = 1
      routes.all.reverse = ""
      ]]></ac:plain-text-body></ac:macro>

    <p>I do not see a need to specify /page(\d+) on the first page so I have created 2 routes. One with the page defaulted to 1 and one with a page variable both pointing to the same action controller. (To see an example of this type of pagination head over to <a class="external-link" href="http://digg.com/">http://digg.com/</a> or <a class="external-link" href="http://digg.com/page1">http://digg.com/page1</a>). The urls are created correctly when the first "allpaged" route is used but lets say that we are on the first page, for example <a class="external-link" href="http://localhost/">http://localhost/</a>, and are using the "all" route. The url assembler does not know what to do for page 2 or higher and simply gives them <a class="external-link" href="http://localhost/">http://localhost/</a> when page 2 and up should go to <a class="external-link" href="http://localhost/page2">http://localhost/page2</a>. </p>

    <p>Hopefully I am not doing something wrong which is causing this problem. </p>

    1. Jun 20, 2008

      <p>Hi Zeljko,</p>

      <p>Thanks for writing. A few points:</p>

      <ol>
      <li>You'll need to use the latest revision of Zend Framework in SVN. The Router has an assemble() method there.</li>
      <li>The fact it tried to enter _assignPageUrls() at all without you calling setRouteName() appears to be a bug. It should probably use getCurrentRouteName() instead if setRouteName() is not specified.</li>
      <li>You need to specify a default value for :page in your first route if you want to call it without a page value. See the Router documentation for more information. However, I will make this the internal default so that you won't have to do this in the future.</li>
      </ol>

      1. Jun 20, 2008

        <p>Thanks for the help!</p>

        <p>I did not think to check the svn for updates. The new framework snapshot clears up lots of issues I had. Unfortunately it still seems to grab the "default" router in _assignPageUrls() rather than the current so I simply replaced $routeParams = $this->_adapter->getRouteParams(); with $routeName = $router->getCurrentRouteName(); and now everything works fine. </p>

        <p>I fixed my routes using your suggestion too =) </p>

        <p>Here is my new route which replaces my previous two, in case anyone needs it.</p>
        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        routes.all.type = "Zend_Controller_Router_Route_Regex"
        routes.all.route = "(?:page(\d+))?"
        routes.all.defaults.controller = "index"
        routes.all.defaults.action = "index"
        routes.all.defaults.page = 1
        routes.all.map.1 = "page"
        routes.all.reverse = "page%d"
        ]]></ac:plain-text-body></ac:macro>

        <p>Also, I think it would be nice to be able to configure a paginator/adapter in my bootstrap and have it made available for later use by creating an instance and setting a $select statement or passing data. Similar to how you can configure a db adapter and pass it to Zend_Db_Table using Zend_Db_Table_Abstract::setDefaultAdapter() which enables you to simply create a table instance without worrying about configuring it later on.</p>

        <p>Somehow I dont think most of the following belongs in my controller class asides from setting the $select and current page.</p>
        <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
        <?php
        class IndexController extends Zend_Controller_Action
        {
        ...
        protected function paginate($select)

        Unknown macro: { $default = Zend_Registry}

        ...
        }
        ]]></ac:plain-text-body></ac:macro>

        <p>I guess what I am saying is that it would be nice if you could simply go like this</p>
        <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
        //create instance of Items model
        $items = new Items();
        $select = $items->selectAll($category);

        //create instance of Paginator
        $paginator = new Paginator();
        $paginator->setData($select)
        ->setCurrentPage($page);
        $this->view->pages = $paginator->getPages();
        ]]></ac:plain-text-body></ac:macro>

        1. Jun 21, 2008

          <p>Yeah, I see what you're saying. I think the solution to this is to add a static Zend_Paginator::setConfig() that can be called in the bootstrap. Then one can overwrite those values in the specific adapter should they need to do so.</p>

  13. Jun 22, 2008

    <p>Cause mistake, when used consultations (Joins)<br />
    $itemCount = clone $this->_select;<br />
    $expression = new Zend_Db_Expr('COUNT <ac:emoticon ac:name="yellow-star" /> AS ' . self::ROW_COUNT_COLUMN);<br />
    $itemCount->reset()<span style="text-decoration: line-through;">>from($this</span><span style="text-decoration: line-through;">></span>_select, $expression);<br />
    $this->setItemCount($itemCount);</p>

    <p>I pose the following modifications</p>

    <p>$itemCount = clone $this->_select;<br />
    <span style="color: rgb(153,51,0);">$itemCount->reset(Zend_Db_Select::COLUMNS);</span><br />
    $expression = new Zend_Db_Expr('COUNT<ac:emoticon ac:name="yellow-star" /> ' . self::ROW_COUNT_COLUMN);<br />
    <span style="color: rgb(153,0,0);">$itemCount->columns($expression);</span><br />
    $this->setItemCount($itemCount);</p>

    <p>It generates the following query<br />
    <span style="color: rgb(0,0,255);">select count<ac:emoticon ac:name="yellow-star" /> as zend_paginator_row_count fom mytable Where ......</span></p>

    <p>And no matter if they are no longer columns composed mistake</p>

    1. Jun 23, 2008

      <p>Could you explain exactly what error it causes? And what query are you using?</p>

      1. Jun 23, 2008

        <p>This is the query<br />
        SELECT `t1`. * , `t2` . * FROM `nb_employee` AS `t1` <br />
        LEFT JOIN `nb_employee_dsc` AS `t2` ON t1.emp_id = t2.emp_id</p>

        <p>This is the query that generates method getPages()<br />
        SELECT COUNT( * ) AS zend_paginator_row_count FROM (SELECT `t1` . *, `t2` . * FROM `nb_employee` AS `t1` LEFT JOIN `nb_employee_dsc` AS `t2` ON t1.emp_id = t2.emp_id) AS `t`</p>

        <p>which caused this error<br />
        Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE<ac:link><ri:page ri:content-title="42S21" /></ac:link>: Column already exists: 1060 Duplicate column name 'emp_id'' </p>

        <p>With the changes you make generates the following query<br />
        SELECT COUNT( * ) AS zend_paginator_row_count FROM `nb_employee` AS `t1` LEFT JOIN `nb_employee_dsc` AS `t2` ON t1.emp_id = t2.emp_id</p>

        <p>Do not cause any errors, is more clean</p>

        <p>$itemCount->reset(Zend_Db_Select::COLUMNS);<br />
        $itemCount->columns($expression); // <br />
        This method was published recently for Zend Framework, <br />
        You can consult the manual and you will find</p>

        <p>Benjamin Gonzales<br />
        Benjamin.gonzales@gmail.com </p>

        1. Jun 24, 2008

          <p>Thanks : )<br />
          I've researched this and updated the proposal accordingly. Please see the Theory of Operation for more details.</p>

  14. Jun 27, 2008

    <ac:macro ac:name="unmigrated-wiki-markup"><ac:plain-text-body><Unable to render embedded object: File ([CDATA[Hi) not found.

    I followed the demo from the website (latest version, SVN), and I have the IndexController like this:

    <?php

    require_once 'Zend/View/Helper/PaginationControl.php';

    class IndexController extends Zend_Controller_Action
    {
    public function indexAction()
    {
    $sampleData = array();
    foreach (range(1, 80) as $number)

    Unknown macro: { $sampleData[] = $number; }


    require_once 'Zend/Paginator.php';
    $paginator = Zend_Paginator::factory($sampleData);
    $paginator->setRouteName('demo')
    >setCurrentPageNumber($this>_getParam('page'));
    $this->view->pages = $paginator->getPages();
    }

    and the demo.ini like
    [demo]
    #

    1. Sample pagination options
      #
      pagination.itemCountPerPage = 10
      pagination.pageRange = 10
      pagination.scrollingStyle = Zend_Paginator_ScrollingStyle_Sliding

    #

    1. Routes
      #
      routes.demo.route = ":adapter/:paginationControl/:scrollingStyle/:itemCountPerPage/:pageRange/:page"
      routes.demo.defaults.controller = index
      routes.demo.defaults.action = index
      routes.demo.defaults.adapter = Array
      routes.demo.defaults.paginationControl = search
      routes.demo.defaults.scrollingStyle = Sliding
      routes.demo.defaults.itemCountPerPage = 10
      routes.demo.defaults.pageRange = 10
      routes.demo.defaults.page = 1
      routes.demo.reqs.adapter = "\w+"
      routes.demo.reqs.scrollingStyle = "\w+"
      routes.demo.reqs.itemCountPerPage = "\d+"
      routes.demo.reqs.pageRange = "\d+"
      routes.demo.reqs.page = "\d+"

    and the index.php like:
    // Set up router
    $configuration = new Zend_Config_Ini('../application/demo.ini', 'demo');
    $router = new Zend_Controller_Router_Rewrite();
    $router->addConfig($configuration, 'routes');

    ... and I get an errot message when I run the page: Fatal error: Uncaught exception 'Zend_Paginator_Exception' with message 'Route "demo" not found' in C:\Programme\wamp\library\Zend\Paginator\Adapter\Abstract.php:280 Stack trace: #0 ...

    Can anybody help me !?
    Thanks.]]></ac:plain-text-body></ac:macro>

  15. Jun 24, 2008

    <p><ac:link><ri:page ri:content-title="39:15 2008" ri:space-key="Tue Jun 24 16" /></ac:link> <ac:link><ri:page ri:content-title="error" /></ac:link> <ac:link><ri:page ri:content-title="client 192.168.1.2" /></ac:link> PHP Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE<ac:link><ri:page ri:content-title="42S21" /></ac:link>: Column already exists: 1060 Duplicate column name 'picid'' in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Statement\\Pdo.php:238\nStack trace:\n#0 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Db<br class="atl-forced-newline" />Statement.php(283): Zend_Db_Statement_Pdo->_execute(Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
    Adapter<br class="atl-forced-newline" />Abstract.php(405): Zend_Db_Statement->execute(Array)\n#2 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter
    Pdo<br class="atl-forced-newline" />Abstract.php(205): Zend_Db_Adapter_Abstract->query(Object(Zend_Db_Select), Array)\n#3 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Db<br class="atl-forced-newline" />Select.php(569): Zend_Db_Adapter_Pdo_Abstract->query(Object(Zend_Db_Select))\n#4 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator
    Adapter<br class="atl-forced-newline" />DbSelect.php(84): Zend_Db_Select->query()\n#5 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator
    Adapter<br class="atl-forced-newline" />DbSelect.php(123): Zend_Paginator_Adapter_DbSelect->setRowCount(Object(Zend_Db_Select))\n#6 D:\\Apache2.2\\htdocs\\libraries
    Zend<br class="atl-forced-newline" />Paginator.php(379): Zend_Paginator_Adapter_DbSelect->count()\n#7 D:<br class="atl-forced-newline" />Apache2 in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
    Statement<br class="atl-forced-newline" />Pdo.php on line 238, referer: <a class="external-link" href="http://www.client.com/home/manager/index">http://www.client.com/home/manager/index</a><br />
    <ac:link><ri:page ri:content-title="41:45 2008" ri:space-key="Tue Jun 24 16" /></ac:link> <ac:link><ri:page ri:content-title="error" /></ac:link> <ac:link><ri:page ri:content-title="client 192.168.1.2" /></ac:link> PHP Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE<ac:link><ri:page ri:content-title="42S22" /></ac:link>: Column not found: 1054 Unknown column 'ecw_product.bid,catid,title,amount,price,dealtime,expiretime' in 'field list'' in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Statement\\Pdo.php:238\nStack trace:\n#0 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Db<br class="atl-forced-newline" />Statement.php(283): Zend_Db_Statement_Pdo->_execute(Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
    Adapter<br class="atl-forced-newline" />Abstract.php(405): Zend_Db_Statement->execute(Array)\n#2 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter
    Pdo<br class="atl-forced-newline" />Abstract.php(205): Zend_Db_Adapter_Abstract->query(Object(Zend_Db_Select), Array)\n#3 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Db<br class="atl-forced-newline" />Select.php(569): Zend_Db_Adapter_Pdo_Abstract->query(Object(Zend_Db_Select))\n#4 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator
    Adapter<br class="atl-forced-newline" />DbSelect.php(84): Zend_Db_Select->query()\n#5 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator
    Adapter<br class="atl-forced-newline" />DbSelect.php(123): Zend_Paginator_Adapter_DbSelect->setRowCount(Object(Zend_Db_Select))\n#6 D:\\Apache2.2\\htdocs\\libraries
    Zend<br class="atl-forced-newline" />Paginator.php(3 in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
    Statement<br class="atl-forced-newline" />Pdo.php on line 238, referer: <a class="external-link" href="http://www.client.com/home/manager/index">http://www.client.com/home/manager/index</a></p>

    <p>When i use the following sql statement:<br />
    select * from tablea join tableb on tablea.picid = tableb.picid<ac:link><ri:page ri:content-title="pdo_mysql" /></ac:link><br />
    I encountered that messages.<br />
    Can anybody help me? Thanks.</p>

    1. Jun 24, 2008

      <p>Hi Frank,<br />
      I've researched this and updated the proposal accordingly. Please see the Theory of Operation for more details.</p>

  16. Jun 25, 2008

    <p>Hi Jurrien,<br />
    I spent one day on the Zend_Paginator, thank you for your work, but, when i use the flowing code:<br />
    $paginator->setCurrentPageNumber($this->_getParam('page'))<span style="text-decoration: line-through;">>setItemCountPerPage(1)</span>>setPageRange(5);<br />
    I cannot jump to next page, so i write to this:<br />
    $paginator->setItemCountPerPage(1)<span style="text-decoration: line-through;">>setPageRange(5)</span>>setCurrentPageNumber($this->_getParam('page'));<br />
    It works.<br />
    Please check it.</p>

    1. Jun 25, 2008

      <p>Hi Frank,</p>

      <p>I've committed a patch that should fix this. Please update to the latest version <ac:emoticon ac:name="smile" /><br />
      By the way, did you have any more comments on the workings on the DbSelect adapter, based on the addition in Theory of Operation?</p>

  17. Jun 25, 2008

    <p>I've updated the latest verion 82, I got a error, such as<br />
    <ac:link><ri:page ri:content-title="56:18 2008" ri:space-key="Thu Jun 26 07" /></ac:link> <ac:link><ri:page ri:content-title="error" /></ac:link> <ac:link><ri:page ri:content-title="client 192.168.1.2" /></ac:link> PHP Fatal error: Uncaught exception 'Zend_Db_Select_Exception' with message 'Unrecognized method 'columns()'' in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Select.php:1086\nStack trace:\n#0 <ac:link><ri:page ri:content-title="internal function" /></ac:link>: Zend_Db_Select->__call('columns', Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Paginator<br class="atl-forced-newline" />Adapter
    <br class="atl-forced-newline" />
    DbSelect.php(131): Zend_Db_Select->columns(Object(Zend_Db_Expr))\n#2 D:\\Apache2.2\\htdocs
    libraries<br class="atl-forced-newline" />Zend
    <br class="atl-forced-newline" />
    Paginator.php(415): Zend_Paginator_Adapter_DbSelect->count()\n#3 D:\\Apache2.2\\htdocs
    libraries<br class="atl-forced-newline" />Zend
    <br class="atl-forced-newline" />
    Paginator.php(249): Zend_Paginator->_calculatePageCount()\n#4 D:\\Apache2.2\\htdocs\\Client\\modules
    product<br class="atl-forced-newline" />controllers
    <br class="atl-forced-newline" />
    SellController.php(155): Zend_Paginator->setItemCountPerPage('1')\n#5 D:\\Apache2.2\\htdocs\\libraries
    Zend<br class="atl-forced-newline" />Controller
    <br class="atl-forced-newline" />
    Action.php(502): Product_SellController->listAction()\n#6 D:\\Apache2.2\\htdocs\\libraries\\Zend
    Controller<br class="atl-forced-newline" />Dispatcher
    <br class="atl-forced-newline" />
    Standard.php(293): Zend_Controller_Action->dispatch('listAction')\n#7 D:\\Apache2.2\\htdocs\\libraries
    Zend<br class="atl-forced-newline" />Controller
    <br class="atl-forced-newline" />
    Front.php(914): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Cont in D:\\Apache2.2\\htdocs\\libraries
    Zend<br class="atl-forced-newline" />Db
    <br class="atl-forced-newline" />
    Select.php on line 1086, referer: <a href="http://www.client.com/home/manager/index">http://www.client.com/home/manager/index</a></p>

    <p>I think you don't need change count function.You can write a sql statement like:<br />
    select dba.*, dbb.cola, dbb.colb from dba join dbb on dba.id = dbb.id<br />
    not like:<br />
    select dba.*, dbb.id, dbb.cola, dbb.colb from dba join dbb on dba.id=dbb.id<br />
    It's Ok when you escaped dbb.id or named a alais.</p>

    <p>Now, it does not work for me.<ac:emoticon ac:name="sad" /></p>

    1. Jun 25, 2008

      <p>You will need the latest ZF trunk for this to work. <ac:emoticon ac:name="smile" /></p>

      1. Jun 26, 2008

        <p>Hi Jurrien,</p>

        <p>In the update to the latest version I was given the wrong message.<br />
        In the past I used to update the following code can be flip:<br />
        $select = $db->select();<br />
        $select->from('ecw_product', '*');<br />
        $select->join('ecw_product_picture', 'ecw_product.picid = ecw_product_picture.picid',array('data','picture'));<br />
        $select->where('ecw_product.seller = ?', $this->_uid);<br />
        $select->where('ecw_product.state = ?', '1');</p>

        <p>$paginator = Zend_Paginator::factory($select);<br />
        $paginator->setItemCountPerPage($paper->product->sell->itemCountPerPage)<br />
        <span style="text-decoration: line-through;">>setPageRange($paper</span>>product->sell->pageRange)<br />
        <span style="text-decoration: line-through;">>setCurrentPageNumber($this</span>>_getParam('page'));</p>

        <p>I get the error message is 'Unrecognized method columns()',I use Zend framework 1.5.2.<br />
        Please give me some suggestions, thank you.</p>

        1. Jun 26, 2008

          <p>As I said, you will need the latest ZF trunk. You can get it from subversion:
          <a class="external-link" href="http://framework.zend.com/download/subversion">http://framework.zend.com/download/subversion</a></p>

          1. Jun 26, 2008

            <p>I downloaded the original version, the problem has been resolved.</p>

  18. Jul 02, 2008

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Official Comment</ac:parameter><ac:rich-text-body>

    <p>This proposal is accepted for development into the <strong>Standard Incubator</strong> with the following provisions:</p>

    <ul>
    <li>The name is changed from Zend_Paginator to Zend_Data_Paginator</li>
    </ul>

    </ac:rich-text-body></ac:macro>

    1. Jul 02, 2008

      <p>Hi Ralph,</p>

      <p>That's great to hear! Jurriën and I discussed the naming issue and we can see both sides. However, we both prefer Zend_Paginator. Here are a few reasons why I think Zend_Paginator is more intuitive:</p>

      <p>1. Consider a new user who is looking for a way to paginate search results and is investigating Zend Framework. He might look at the components available, not see a pagination component, not think to look in Zend_Data (I wouldn't), and conclude that Zend Framework simply does not have one. We feel that every decision that is made with respect to naming, API, and organization of components should take new users into consideration.</p>

      <p>2. Ontology is overrated. I highly, highly encourage you to read <a href="http://www.shirky.com/writings/ontology_overrated.html">this talk</a> on the subject. Take special note of the sections entitled <a href="http://www.shirky.com/writings/ontology_overrated.html#fortune_telling">"Fortune Telling"</a> and <a href="http://www.shirky.com/writings/ontology_overrated.html#great_minds_dont_think_alike">"Great Minds Don't Think Alike"</a>.</p>

      <p>An artificial hierarchy necessarily has assumptions built into it: in this case, that a user would classify pagination under Data. But that assumption is faulty, because it assumes users all think alike, and categorize things in a similar way to the ontologists. In reality, a user might classify pagination under any number of plausible topics: Search (if they primarily consider the search results use case), View (if they primarily consider pagination a UI feature), and Db (if they fail to consider the possibility of paginating data from data sources other than databases).</p>

      <p>FWIW, we don't consider Paginator solely a data component.</p>

      <p>3. It would be pretty lonely in a Zend_Data namespace. There are no proposals for any data manipulation components currently (there's a Zend_Tree, but it's archived and inactive).</p>

      <p>4. If there were concerns about the number of top-level components, one would expect Zend_JavaScript_Dojo instead of Zend_Dojo (since there will inevitably be components for Prototype, ExtJs, etc.), Zend_Auth_OpenId instead of Zend_OpenId, etc.</p>

      <p>Thoughts? <ac:emoticon ac:name="smile" /></p>

      1. Jul 17, 2008

        <p>+1 for Zend_Paginator</p>

  19. Jul 07, 2008

    <p>Please forgive me if this has been explained, I couldn't find it. Is it possible for this component to (when working with db results) add a limit clause to the query? How would one go about adding a limit to the query based on the paginator's position?</p>

  20. Jul 18, 2008

    <p>I found another bug, problems occur when the query used DISTINC and GROUP BY, I will switch the notes and how to solve</p>

    1. Jul 19, 2008

      <p>Thanks. We anticipated problems with DISTINCT and GROUP BY problems with our current approach. Unfortunately, there really isn't a nice solution for this. The best solution would be to write your own count query and provide it to the DbSelect adapter. Another solution could be to detect a DISTINCS or GROUP BY clause and use the sub-query approach in those cases. However, from a performance point of view I'm not sure that's the way to go... thoughts?</p>

      1. Jul 25, 2008

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        //actual
        public function count(){
        if ($this->_rowCount === null)

        Unknown macro: { $expression = new Zend_Db_Expr('COUNT(*) AS ' . self}

        return $this->_rowCount;
        }

        // proposal
        public function count(){
        if ($this->_rowCount === null)

        Unknown macro: { $rowCount = clone $this->_select; $group = implode(",",$rowCount->getPart(Zend_Db_Select}

        return $this->_rowCount;
        }

        tried and works very well in my consultations,

        Benjamin Gonzales
        benjamin.gonzales@gmail.com
        ]]></ac:plain-text-body></ac:macro>

  21. Jul 19, 2008

    <p>First of all I really like this paginator, it is very easy to use and configure.<br />
    However I had some problems using it with the iterator adapter, this since I was using it on a result set of an Zend_Db_Table_Abstract::fetchAll that returns an instance of Zend_Db_Table_Rowset which made calls to Zend_Paginator::getItem impossible since the call $page->count() returned null. Am I missing something or doing something that is not be supported?</p>

    <p>The follwoing patch solves the problem at least for me at the time...</p>

    <p>Once again good work and thanks for this component.</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    — library/Zend/Paginator.php (revision 10207)
    +++ library/Zend/Paginator.php (working copy)
    @@ -429,8 +429,9 @@
    }

    $page = $this->getItemsByPage($pageNumber);
    + $itemCount = $this->getItemCount($page);

    • if ($page->count() == 0) {
      + if ($itemCount == 0)
      Unknown macro: { /** * @see Zend_Paginator_Exception */@@ -440,7 +441,7 @@ . 'Probably no data to paginate.'); }
    • if ($itemNumber > $page->count())
      Unknown macro: {+ if ($itemNumber > $itemCount)
      Unknown macro: { /** * @see Zend_Paginator_Exception */@@ -449,7 +450,16 @@ throw new Zend_Paginator_Exception('Page ' . $pageNumber . ' does not' . ' contain item number ' . $itemNumber); }
      -++ if ($page instanceof LimitIterator)+
      Unknown macro: {+ $offset = ($pageNumber - 1) * $itemCount;+ $position = $offset + ($itemNumber - 1);++ $page->seek($position);+ return $page->current();+ }
      + return $page[$itemNumber - 1]; }

       
      ]]></ac:plain-text-body></ac:macro>

    1. Jul 19, 2008

      <p>This should be fixed now. Could you paste any error message you might still get, just to be sure?</p>

      1. Jul 19, 2008

        <p>With the latest I get the following error: "Fatal error: Cannot use object of type LimitIterator as array in /home/johan/workspace/php/zf/standard/library/Zend/Paginator.php on line 454"</p>

        <p>That is probably because of <code>return $page<ac:link />;</code> that why I also had to add the check <code>if ($page instanceof LimitIterator)</code> and seek the postion in the iterator.</p>

  22. Jul 31, 2008

    <p>Is there an example planned with another templating system like Smarty ? Tried with the nice Naneau's wrapper of smarty, I can pass an array of "setItemCountPerPage(n" objects but can't properly set the pageCount, previous, next view thingies in my_pagination_controls.tpl, any clue ?</p>

    1. Jul 31, 2008

      <p>Hi Fabrice,</p>

      <p>The array with these values is usable from Smarty as-is. Instead of using the PaginationControl view helper, you should do something like this:</p>

      <p>In the controller:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $smarty->assign('pages', $paginator->getPages());
      ]]></ac:plain-text-body></ac:macro>
      <p>In the view:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      {$pages.pageCount}
      ]]></ac:plain-text-body></ac:macro>

      1. Aug 05, 2008

        <p>Thank you Matthew, (sorry for late response),</p>

        <p>This is the Search pattern pagination controls a la Smarty :</p>

        <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
        <!--
        See http://developer.yahoo.com/ypatterns/pattern.php?pattern=searchpagination
        -->

        Unknown macro: {if $this->pages->pageCount}

        <div id="paginationControl">
        <!-- Previous page link -->

        Unknown macro: {if $this->pages->previous }

        <a href="{$baseUrl}/vol/index/page/{$this->pages->previous}" >< Previous</a> |

        Unknown macro: {else}

        <span class="disabled">< Previous</span> |

        Unknown macro: {/if}

        <!-- Numbered page links -->

        Unknown macro: {foreach from=$this->pages->pagesInRange key=myId item=i}
        Unknown macro: {if $this->pages->page != $this->pages->current }

        <a href="{$baseUrl}/vol/index/page/{$i}" >{$i}</a> |

        {$i} |

        Unknown macro: {/if}
        Unknown macro: {/foreach}

        <!-- Next page link -->

        Unknown macro: {if $this->pages->next}

        <a href="{$baseUrl}/vol/index/page/{$this->pages->next}" >Next ></a>

        Unknown macro: {else}

        <span class="disabled">Next ></span>

        </div>

        Unknown macro: {/if}

        ]]></ac:plain-text-body></ac:macro>
        <p>With Naneau View Helper, in the controller :</p>
        <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
         $sampleData = $this->objet->fecthAll()->toArray();
        $paginator = new Zend_Paginator(new Zend_Paginator_Adapter_array($sampleData));
        //$paginator = Zend_Paginator::factory($sampleData);
        $paginator->setItemCountPerPage(3)
        ->setCurrentPageNumber($page);
        $this->view->pages = $paginator->getPages();
        $this->view->objets= $paginator;

        ]]></ac:plain-text-body></ac:macro>

  23. Aug 01, 2008

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    //actual
    public function count(){
    if ($this->_rowCount === null)

    Unknown macro: { $expression = new Zend_Db_Expr('COUNT(*) AS ' . self}

    return $this->_rowCount;
    }

    // proposal
    public function count(){
    if ($this->_rowCount === null)

    Unknown macro: { $rowCount = clone $this->_select; $group = implode(",",$rowCount->getPart(Zend_Db_Select}


    return $this->_rowCount;
    }

    ]]></ac:plain-text-body></ac:macro>

    <p>tried and works very well in my consultations, </p>

    <p>Benjamin Gonzales <br />
    benjamin.gonzales@gmail.com
    <a class="external-link" href="http://codigolinea.com">http://codigolinea.com</a></p>

    1. Aug 02, 2008

      <p>Hi Benjamin,</p>

      <p>Looks good, I'll give that a try <ac:emoticon ac:name="smile" /><br />
      Could you please file an issue for this?</p>

      1. Aug 05, 2008

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        //Original query
        SELECT `t1`.`emp_id`, `t1`.`emp_name`, `t1`.`emp_lastname1`, `t1`.`emp_lastname2`, `t1`.`emp_sex`,
        `t1`.`emp_status`, COALESCE(ds_newface,"0") as ds_newface,
        COALESCE(ei_img1,"defualt/1_icon.png") as ei_img1
        FROM `nb_employee` AS `t1`
        LEFT JOIN `nb_employee_dsc` AS `t2` ON t1.emp_id = t2.emp_id
        LEFT JOIN `nb_employee_img` AS `t3` ON t1.emp_id = t3.emp_id
        WHERE (t1.emp_status < 2)
        GROUP BY `t1`.`emp_id`

        //transform query count
        SELECT COUNT(DISTINCT t1.emp_id) AS zend_paginator_row_count
        FROM `nb_employee` AS `t1`
        LEFT JOIN `nb_employee_dsc` AS `t2` ON t1.emp_id = t2.emp_id
        LEFT JOIN `nb_employee_img` AS `t3` ON t1.emp_id = t3.emp_id
        WHERE (t1.emp_status < 2)
        ]]></ac:plain-text-body></ac:macro>