Thinking about the pagination feature

Published on and tagged with cakephp  pagination

Recently, I was asked by yunhaihuang some questions about advanced pagination stuff I couldn’t answer satisfactorily…

And that’s when I realized that the current implementation of the pagination feature of CakePHP contains at least one conceptual flaw (in most pagination scenarios you probably won’t notice this flaw).

Let’s have a look at pagination at the model level. A paginator is in principle a user of the “find” functionality of a model. And as there are many different ways to use this “find” functionality, there can also be many paginators. So we have the following relationship: a model is accessed by many paginators.

In the code you don’t find an explicit paginator object, you define the “paginators” with the $paginate array of the controller. With one exception: if you want to use a custom query for the pagination you have to add and implement the methods paginate() and paginateCount() in your model (see the manual for more details). But there is a snag to it: as soon as you define those methods it is no longer possible to use the default pagination query without workarounds, because now the new methods are always used… So we can say that the implemented concept is: a model has one pagination query which can be accessed by many paginators.

Another thing I wondered at was the pagination helper. Shouldn’t there be different instances of this helper if you want to paginate different data sets on the same page? Currently, there is only one instance, and every method allows you to specify the model name to switch the data set (it assumes there is only one data set per model). But I’m not sure whether the pagination helper is even thought for this use case, at least I couldn’t figure out how to paginate two data sets (without Ajax)…

Hopefully these thoughts help to improve the pagination feature in future versions.

17 comments baked

  • nate

    Your idea that this is a flaw is incorrect, as the ability for scoping different sets of paginated records has been there at the foundational level since the beginning, but was never fully developed because it was never needed or asked-for.

    Also, your point about the paginate/paginateCount methods is pretty much moot, since you can define as many alternative types of finders for the find() method as you want, and/or pass in custom flags in the options array.

  • cakebaker

    @nate: Thanks for your comment!

    Yes, you can create your own finders and work with custom flags, but I think a solution with an “interface” defining the paginate/paginateCount methods would be cleaner.

  • nate

    Possibly, but in retrospect, I really don’t think I like that solution. It still seems a bit error-prone.

  • francky06l

    What I found somehow restrictive in paginate, is that the counting is not “customizable”.
    I had to paginate, with the search on a classic hasMany (but the search was on the second model). I can achieve what I want by changing the bindings (hasMany becomes hasOne) .. usage of containbale etc .., and I query “DISTINCT id,…”, that works perfect.
    The problem is the count, it’s not able to make a COUNT(DISTINCT id) as I do in the field list.
    Well no problem, I wrote a paginateCount (but not paginate) function .. But that can be a problem (solvable) when you have the need for several paginateCount ..

  • brian

    I’m fairly new to cakephp, and I’m curious as to why paginate is part of the model. Isn’t the model better suited for business logic specific to that model? It seems like paginate is an interface issue and would be better suited somewhere else, where it defines the parameters needed by the interface which are then used in the standard find() methods.

    Again, I’m new to this, so I’m truly asking for my own education.

  • cakebaker

    @all: Thanks for your comments!

    @francky06l: Yes, your scenario is quite tricky ;)

    @brian: I don’t know why paginate()/paginateCount() are in the model, it doesn’t make much sense to me. As you already said, they represent an interface which would be better suited somewhere else.

  • rick

    So that’s why I’ve stumbled into this problem. It’s because there really is a problem with the Paginator in Cake.

    So what were your solutions to implementing a COUNT(DISTINCT field) in your queries?

  • cakebaker

    @rick: You could implement paginate() and paginateCount() in your model for this purpose. But I’m not sure whether this is the best solution, maybe someone in the CakePHP Google Group has a better idea.

    Hope that helps!

  • Pixelastic

    As noted before, first comment : “Also, your point about the paginate/paginateCount methods is pretty much moot, since you can define as many alternative types of finders for the find() method as you want, and/or pass in custom flags in the options array.”

    Can please somebody expand on that ? I’m stuck on how to define different pagination rules for one same controller.
    I need a special paginate for the admin panel and another for the public page (the latter is quite complicated and needs a very special treatment), but I would like to find a more generic way to define multiple pagination rules for the same controller.

  • Pixelastic

    @myself : I have found a way, I just have to :

    1/ create a Model::paginateIndex and Model::paginateCountIndex methods for paginating my index page. (and multiple other methods for each special pagination I will need)

    2/ Overriding the AppModel::paginate and AppModel::paginateCount methods with my own, checking for the $conditions params and if a string (eg. ‘index’) returning the corresponding method in the model ( $paginateFunction = $paginate”.Inflector::camelize($conditions); ) and returning $this->{$paginateFunction}([…]);
    (same goes for paginateCount)

    3/ Calling my paginate action in my controller like
    $this->paginate = array(‘conditions’ => ‘index’);
    $itemList = $this->paginate();

  • cakebaker

    @Pixelastic: I’m glad you found a solution. Personally, I wouldn’t add paginate() and paginateCount() to the AppModel if it is not really necessary, but else your approach looks fine.

  • Pixelastic

    I could have limited myself to adding paginate and paginateCount to the only model that actually needed such a treatment, but I think multiple custom pagination could be useful in the future for other models.

  • cakebaker

    @Pixelastic: Do you know YAGNI? ;-)

  • Pixelastic

    Hmm, you’re right, maybe I should limit myself only to the model(s) who actually need it.

  • Fabricio Sodano

    Hello…

    This is an old topic, I know… but as today there was no way to fix this (The DISCTINCT not being applied to the count) however, i’ve found that in the controller.php file, the paginate method has this line to fetch the count for the pagination:

    $count = $object->find(‘count’, array_merge($parameters, $extra));

    However, when you go and look into the paginate method, the 2nd param is the “fields” array, so… I’ve changed the previous line to:

    $count = $object->find(‘count’, $fields);

    And now it works fully. However, I’m pretty sure it will have side effects, but I haven’t seen any yet :)

    My question is: Is there another way to implement this solution *specifically* for this particular case instead of the full app?

    Cheers & Thanks!

  • cakebaker

    @Fabricio: Thanks for your comment!

    The only solution I see at the moment is to copy the paginate() method from the Controller class and to add this method to your own controller. Then you can apply your change there.

    And regarding side effects, you could run the test case for the Controller class, and see whether it will fail ;-)

    Hope that helps!

  • Fabricio Sodano

    All right! I’ll try that.

    Thanks a lot!

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License