2

I'm working inside a legacy Cake PHP 2.10 application and am utilising both the Pagination component and PaginationHelper.

I've had to override the paginateCount method to set a custom option for my paginator settings to limit the maximum number of query results which are then paginated, limit and maxLimit are not sufficient on their own - I've added totalLimit.

The problem now is if there are less results than my totalLimit, such as when filtering, it continues to display 20,000 rather than the number of actual results. I could create another query specifically for this count, but wondered if there's a quick workaround that I'm missing, here's my my Application model method:

public function paginateCount($conditions = null, $recursive = 0, $extra = array())
{
        if (isset($extra['totalLimit'])) {
                return $extra['totalLimit'];
        }
}

And my pagination set up:

// define pagination settings
$this->Paginator->settings = array(
        'Application' => array(
                'paramType' => 'querystring',
                'totalLimit' => 20000,
                'limit' => $filters['pagination']['perPage'],
                'maxLimit' => $filters['pagination']['perPage'],
                'fields' => array(
                        'Application.*',
                        'ApplicationPayday.*',
                        'ApplicationApiLink.*',
                        'ApplicationResponse.*',
                        'AffiliateId.*',
                        'Redirect.*'
                ),
                'joins' => array(
                        array(
                                'table' => 'tlp_application_paydays',
                                'alias' => 'ApplicationPayday',
                                'type' => 'LEFT',
                                'conditions' => array(
                                        'ApplicationPayday.application_id = Application.id'
                                )
                        ),
                        array(
                                'table' => 'tlp_application_api_links',
                                'alias' => 'ApplicationApiLink',
                                'type' => 'LEFT',
                                'conditions' => array(
                                        'ApplicationApiLink.application_id = Application.id'
                                )
                        ),
                        array(
                                'table' => 'tlp_application_responses',
                                'alias' => 'ApplicationResponse',
                                'type' => 'LEFT',
                                'conditions' => array(
                                        'ApplicationResponse.application_id = Application.id'
                                )
                        ),
                        array(
                                'table' => 'tlp_affiliate_ids',
                                'alias' => 'AffiliateId',
                                'type' => 'LEFT',
                                'conditions' => array(
                                        'AffiliateId.aff_id = Application.tlp_aff_id'
                                )
                        ),
                        array(
                                'table' => 'tlp_redirects',
                                'alias' => 'Redirect',
                                'type' => 'LEFT',
                                'conditions' => array(
                                        'Redirect.application_id = Application.id'
                                )
                        )
                ),
                'conditions' => $queryConditions,
                'group' => array(
                        'Application.id'
                ),
                'order' => array(
                        'Application.id' => 'desc'
                ),
                'recursive' => -1
        )
);

// run query to get applications via paginated settings
try {
        $applications = $this->Paginator->paginate('Application');
} catch (\NotFoundException $e) {
        $this->Session->setFlash("Page doesn't exist. We've reset your search filters and taken you to the first page.");
        return $this->redirect('/payday_admin/leads/');
}
Ryan H
  • 2,620
  • 4
  • 37
  • 109

1 Answers1

2

The problem is that you replace the number of total results that paginateCounts returns with $extra['totalLimit'] that you set (20.000 in this case).

The pagineCount() function should be overridden with something like in the example below. This way you will insert your total limit in the ->find('count', []) parameters and also keep the original count if your $extra['totalLimit'] parameter is not sent.

public function paginateCount($conditions, $recursive, $extra)
{
    if (isset($extra['totalLimit'])) {
        $limit = $extra['totalLimit'];
        unset($extra['totalLimit']);

        $count = $this->find('count', compact($conditions, $recursive, $limit, $extra));
        return (int)$count;
    }

    $count = $this->find('count', compact($conditions, $recursive, $extra));
    return (int)$count;
}

Your count will be limited to the maximum value of totalLimit, but will return the true count if it's lower.

If you have millions of rows with many joins I recommend caching the count. The cache key can be created by hashing the conditions and other parameters.


This is how the conditions are passed to the overridden paginateCount() from the model Class.

$this->Paginator->settings = [
    'limit' => 50,
    'contain' => [
        ...
    ],
    'conditions' => $conditions,
    'joins' => $joins,
    'group' => $group
];

$this->Paginator->paginate('Model');
Valeriu Ciuca
  • 2,084
  • 11
  • 14
  • This is interesting, in practice though it's not limiting my query at all. I have millions of rows, and lots of joins. Before your answer I created a separate query, moved the contents of `$this->Paginator->settings` into there and did a `$this->Application->find('count', array())` and passed the output to the paginator. Why would your approach described be taking so long? I don't think it's limiting the query – Ryan H Oct 18 '22 at 13:09
  • If you have millions of rows with many joins it's normal to take some time. I am in a similar situation, and I limit the result by date. Last 3 months for example, and if the user needs a longer period of time he has to increase the date range. I also cache the count. The cache key is created by hashing the conditions and other parameters. – Valeriu Ciuca Oct 18 '22 at 13:27
  • I think I see why it's so slow, in the `if` statment for the `totalLimit`, when I log the `$conditions` they're empty. This in affect means it would pull hundreds of millions of rows from my table, yet, if you refer to my original post, everything is listed in the paginator `Application`. How would I pass these conditions? – Ryan H Oct 18 '22 at 13:34
  • Looks like `compact()` is resetting my conditions, so logging `$conditions` and `$recursive` and `$extra` all independently gives me the correct values, but when using `compact()` I get an empty array, thus no query conditions applied which is resulting in the query count taking too long. – Ryan H Oct 18 '22 at 13:40
  • Hmm, you've got the idea, work to obtain a good array to be used in `find` `count`. – Valeriu Ciuca Oct 18 '22 at 13:45
  • Use array_merge($conditions, $recursive, $extra, $limit) and $limit should be an array like $limit = ['limit' =>$extra['totalLimit']] – Valeriu Ciuca Oct 18 '22 at 14:11