Added by Jurrien Stutterheim, last edited by Matthew Ratzloff on Sep 24, 2008  (view change)

Labels

 

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: 44)

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.

Come with me if you want to paginate.

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

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

Paginate more clearly covers the load IMHO. But then, I'm colorblind

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

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.

Posted by Matthew Ratzloff at May 19, 2008 21:06 Updated by Matthew Ratzloff

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

Jurriën and I are currently working jointly on something that is based on Zym_Paginate, but hopefully more flexible.

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.

Something like:

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

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

Actually COUNT query is executed on reseted clone of select object:

But consider this example

Since count-select is reseted, it will miss where 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 Db_Select in trunk and reseting COLUMNS part and with columns() method.

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

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.

As discussed in IRC:

Hi Ben,

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.

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

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.

It's great, well done guys

Posted by fc at Jun 15, 2008 16:46

Thanks, Federico!

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.

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.

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.

Hi Nico,

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.

If you have any more suggestions, though, please post them. Thanks!

Does the method setCountSelect() support MySQL for the following statements?

SELECT SQL_CALC_FOUND_ROWS * FROM mytable;

$customSelectQuery = "SELECT FOUND_ROWS()";

$paginator->setCountSelect($customSelectQuery);

This is assuming you subclass Zend_Db_Table_Select and add a method for SQL_CALC_FOUND_ROWS.

could you add SQL_CALC_FOUND_ROWS support only for MySQL adapter? it's really better than count.
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 ...
it will return rows with limit rules! it just calculates total rows regardless of limit params.
it's good replacement for SELECT COUNT ... FROM (SELECT ...))

Thank you.

Posted by Nikolay Mogulev at Jun 17, 2008 12:27 Updated by Nikolay Mogulev

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.

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.

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.

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.

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.

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.

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.

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.

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.

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

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.

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.

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.

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:

  • "Concise API to access the 'page state'": This is Zend_Paginator_Page.
  • "View helper which ... returns a page naviation UI": This is Zend_View_Helper_PaginationControl.
  • "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.

I believe part of your confusion is from the multiple 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:

Or you can do this:

Or this:

Which is another way of saying this:

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.

That's reasonable.

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.

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.

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.

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.

"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.

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.

My description was pretty abstract, so if you think that it matches the current implementation, then at least we both agree on intention. 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.

What I was describing would imply a usage like the following:

Here are the key differences:

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.

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.

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.

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.

View the rest of this thread. Most recent comment: Jul 03, 2008
2 more comments by: Nico Edtinger, Bryce Lohr

"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.

"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.

"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.

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.

"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.

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.

Hi Bryce,

I encourage you to check out the latest version in trunk.

- 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.

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().

- 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.

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.)

  • 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.
  • 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.
  • 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.

Nice going! All of this was done pretty much exactly as you described.

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

View the rest of this thread. Most recent comment: Jun 29, 2008
5 more comments by: Jurrien Stutterheim, Bryce Lohr

Great work on the Zend_Paginator thus far =)

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.

I put the following code into my controller:

This gives an error on the last line in the getPages() function

if we go to line 229 of PageSet.php we find the following function

I have had several problems with this function:

  1. There seems to be a typo on line 229 because the $router object has no assemble function
  2. This function does not get the current route, instead it seems to always use "default"

In order to get it working I had to make the following changes:

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.

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 http://digg.com/ or http://digg.com/page1). The urls are created correctly when the first "allpaged" route is used but lets say that we are on the first page, for example http://localhost/, and are using the "all" route. The url assembler does not know what to do for page 2 or higher and simply gives them http://localhost/ when page 2 and up should go to http://localhost/page2.

Hopefully I am not doing something wrong which is causing this problem.

Posted by Zeljko Fedoran at Jun 20, 2008 08:43 Updated by Zeljko Fedoran

Hi Zeljko,

Thanks for writing. A few points:

  1. You'll need to use the latest revision of Zend Framework in SVN. The Router has an assemble() method there.
  2. 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.
  3. 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.

Thanks for the help!

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.

I fixed my routes using your suggestion too =)

Here is my new route which replaces my previous two, in case anyone needs it.

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.

Somehow I dont think most of the following belongs in my controller class asides from setting the $select and current page.

I guess what I am saying is that it would be nice if you could simply go like this

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.

Cause mistake, when used consultations (Joins)
$itemCount = clone $this->_select;
$expression = new Zend_Db_Expr('COUNT AS ' . self::ROW_COUNT_COLUMN);
$itemCount->reset()>from($this>_select, $expression);
$this->setItemCount($itemCount);

I pose the following modifications

$itemCount = clone $this->_select;
$itemCount->reset(Zend_Db_Select::COLUMNS);
$expression = new Zend_Db_Expr('COUNT ' . self::ROW_COUNT_COLUMN);
$itemCount->columns($expression);
$this->setItemCount($itemCount);

It generates the following query
select count as zend_paginator_row_count fom mytable Where ......

And no matter if they are no longer columns composed mistake

Could you explain exactly what error it causes? And what query are you using?

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

This is the query that generates method getPages()
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`

which caused this error
Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'emp_id''

With the changes you make generates the following query
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

Do not cause any errors, is more clean

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

Benjamin Gonzales
Benjamin.gonzales@gmail.com

Posted by Benjamin Gonzales Basilio at Jun 23, 2008 16:20 Updated by Benjamin Gonzales Basilio

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

Posted by Jurrien Stutterheim at Jun 24, 2008 00:53 Updated by Jurrien Stutterheim

Hi!

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.

Posted by gepa at Jun 23, 2008 02:37 Updated by gepa

[Tue Jun 24 16:39:15 2008] [error] [client 192.168.1.2] PHP Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[42S21]: 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
Statement.php(283): Zend_Db_Statement_Pdo->_execute(Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter
Abstract.php(405): Zend_Db_Statement->execute(Array)\n#2 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter\\Pdo
Abstract.php(205): Zend_Db_Adapter_Abstract->query(Object(Zend_Db_Select), Array)\n#3 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
Select.php(569): Zend_Db_Adapter_Pdo_Abstract->query(Object(Zend_Db_Select))\n#4 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator\\Adapter
DbSelect.php(84): Zend_Db_Select->query()\n#5 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator\\Adapter
DbSelect.php(123): Zend_Paginator_Adapter_DbSelect->setRowCount(Object(Zend_Db_Select))\n#6 D:\\Apache2.2\\htdocs\\libraries\\Zend
Paginator.php(379): Zend_Paginator_Adapter_DbSelect->count()\n#7 D:
Apache2 in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Statement
Pdo.php on line 238, referer: http://www.client.com/home/manager/index
[Tue Jun 24 16:41:45 2008] [error] [client 192.168.1.2] PHP Fatal error: Uncaught exception 'Zend_Db_Statement_Exception' with message 'SQLSTATE[42S22]: 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
Statement.php(283): Zend_Db_Statement_Pdo->_execute(Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter
Abstract.php(405): Zend_Db_Statement->execute(Array)\n#2 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Adapter\\Pdo
Abstract.php(205): Zend_Db_Adapter_Abstract->query(Object(Zend_Db_Select), Array)\n#3 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db
Select.php(569): Zend_Db_Adapter_Pdo_Abstract->query(Object(Zend_Db_Select))\n#4 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator\\Adapter
DbSelect.php(84): Zend_Db_Select->query()\n#5 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator\\Adapter
DbSelect.php(123): Zend_Paginator_Adapter_DbSelect->setRowCount(Object(Zend_Db_Select))\n#6 D:\\Apache2.2\\htdocs\\libraries\\Zend
Paginator.php(3 in D:\\Apache2.2\\htdocs\\libraries\\Zend\\Db\\Statement
Pdo.php on line 238, referer: http://www.client.com/home/manager/index

When i use the following sql statement:
select * from tablea join tableb on tablea.picid = tableb.picid[pdo_mysql]
I encountered that messages.
Can anybody help me? Thanks.

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

Posted by Jurrien Stutterheim at Jun 24, 2008 02:53 Updated by Jurrien Stutterheim

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

Hi Frank,

I've committed a patch that should fix this. Please update to the latest version
By the way, did you have any more comments on the workings on the DbSelect adapter, based on the addition in Theory of Operation?

I've updated the latest verion 82, I got a error, such as
[Thu Jun 26 07:56:18 2008] [error] [client 192.168.1.2] 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 [internal function]: Zend_Db_Select->__call('columns', Array)\n#1 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Paginator
Adapter
DbSelect.php(131): Zend_Db_Select->columns(Object(Zend_Db_Expr))\n#2 D:\\Apache2.2\\htdocs\\libraries
Zend
Paginator.php(415): Zend_Paginator_Adapter_DbSelect->count()\n#3 D:\\Apache2.2\\htdocs\\libraries
Zend
Paginator.php(249): Zend_Paginator->_calculatePageCount()\n#4 D:\\Apache2.2\\htdocs\\Client\\modules\\product
controllers
SellController.php(155): Zend_Paginator->setItemCountPerPage('1')\n#5 D:\\Apache2.2\\htdocs\\libraries\\Zend
Controller
Action.php(502): Product_SellController->listAction()\n#6 D:\\Apache2.2\\htdocs\\libraries\\Zend\\Controller
Dispatcher
Standard.php(293): Zend_Controller_Action->dispatch('listAction')\n#7 D:\\Apache2.2\\htdocs\\libraries\\Zend
Controller
Front.php(914): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Cont in D:\\Apache2.2\\htdocs\\libraries\\Zend
Db
Select.php on line 1086, referer: http://www.client.com/home/manager/index

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

Now, it does not work for me.

Posted by frank king at Jun 25, 2008 17:20 Updated by frank king

You will need the latest ZF trunk for this to work.

Posted by Jurrien Stutterheim at Jun 25, 2008 23:13 Updated by Jurrien Stutterheim

Hi Jurrien,

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

$paginator = Zend_Paginator::factory($select);
$paginator->setItemCountPerPage($paper->product->sell->itemCountPerPage)
>setPageRange($paper>product->sell->pageRange)
>setCurrentPageNumber($this>_getParam('page'));

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

As I said, you will need the latest ZF trunk. You can get it from subversion:
http://framework.zend.com/download/subversion

I downloaded the original version, the problem has been resolved.

Posted by frank king at Jun 26, 2008 02:59 Updated by frank king
Zend Official Comment

This proposal is accepted for development into the Standard Incubator with the following provisions:

  • The name is changed from Zend_Paginator to Zend_Data_Paginator

Hi Ralph,

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:

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.

2. Ontology is overrated. I highly, highly encourage you to read this talk on the subject. Take special note of the sections entitled "Fortune Telling" and "Great Minds Don't Think Alike".

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).

FWIW, we don't consider Paginator solely a data component.

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).

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.

Thoughts?

+1 for Zend_Paginator

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?

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

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?

Posted by Benjamin Gonzales Basilio at Jul 25, 2008 09:43 Updated by Benjamin Gonzales Basilio

First of all I really like this paginator, it is very easy to use and configure.
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?

The follwoing patch solves the problem at least for me at the time...

Once again good work and thanks for this component.

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

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"

That is probably because of <code>return $page[$itemNumber - 1];</code> that why I also had to add the check <code>if ($page instanceof LimitIterator)</code> and seek the postion in the iterator.

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 ?

Hi Fabrice,

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

In the controller:

In the view:

Thank you Matthew, (sorry for late response),

This is the Search pattern pagination controls a la Smarty :

<!--
See http://developer.yahoo.com/ypatterns/pattern.php?pattern=searchpagination
-->
{if $this->pages->pageCount}
<div id="paginationControl">
<!-- Previous page link -->
{if $this->pages->previous }
<a href="{$baseUrl}/vol/index/page/{$this->pages->previous}"  >&lt; Previous</a> |
{else}
<span class="disabled">&lt; Previous</span> |
{/if}

<!-- Numbered page links -->
{foreach from=$this->pages->pagesInRange key=myId item=i}
  {if $this->pages->page != $this->pages->current }
    <a href="{$baseUrl}/vol/index/page/{$i}"  >{$i}</a> |
  {else}
    {$i} |
  {/if}
{/foreach}


<!-- Next page link -->
{if $this->pages->next}
<a href="{$baseUrl}/vol/index/page/{$this->pages->next}"  >Next &gt;</a>
{else}
<span class="disabled">Next &gt;</span>
{/if}
</div>
{/if}


With Naneau View Helper, in the controller :

 $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;

tried and works very well in my consultations,

Benjamin Gonzales
benjamin.gonzales@gmail.com
http://codigolinea.com

Posted by Benjamin Gonzales at Aug 01, 2008 18:45 Updated by Benjamin Gonzales

Hi Benjamin,

Looks good, I'll give that a try
Could you please file an issue for this?