Passing query options as paginator settings is no longer supported

SOLVED - Please see below.

I need to separate the index initial load from the other index loads such as from pagination or a redirect from another function for 3 reasons.

  1. To manipulate the sort and direction on initial load to enable DRY coding.

  2. To set the limit from the database because my users have the option to set their own results per page for each controller.

  3. To persist the same result set after a PRG from index.


Heres a copy/paste working example on a baked v4.5.9 which I originally designed in an early 3 branch.


Articles Table

Custom Finder

public function findMyArticles(Query $query, array $options): object
{
    $query
    ->where([
        'user_id' => $options['user_id']
    ]);

    return $query;
}

Articles Controller

Index Method

public function index(string $menuLink = null)
{
    /**
     * This is for the post only to help explain reason 1.
     *
     * In my application I have parent accounts and subsidiary accounts as menu
     * links and the only difference between them is one column. So rather than
     * have two mvc's I separated the initial load from the other loads which
     * enabled me to manipulate this incoming data and DRY code it.
     */
     $menuLink = 'author';

     if (empty($this->request->getQuery('page'))
         && is_null($this->request->getQuery('sort'))
         && is_null($this->request->getQuery('direction'))) {

         /**
          * INITIAL LOAD
          */
         $page = '';

         // For reason 1.
         if ($menuLink === 'author') {
             $sort = 'title';
             $direction = 'asc';
         } else {
             $sort = 'created';
             $direction = 'desc';
         }

         // For reason 2.
         $limit = 2; // This value of 2 comes from the database in my app.

         /**
          * Reason 3.
          *
          * You MUST manually set the sort, direction and limit
          * so they can be passed to the view and stored in hidden fields.
          *
          * Then they can be posted to the updatedelete method and appended
          * to the redirect back to index.
          *
          * This enables the same result set to be displayed on index as
          * there was before the post to the updatedelete method.
          *
          * For example.
          * 1. If your on page 53 with a sort of id asc and limit of 2 and click the update button
          * the database will be updated in the updatedelete method and after the redirect
          * back to index the result set will be page 53 with a sort of id asc and limit of 2.
          *
          * 2. If your on page 53 with a sort of id asc and limit of 2 and click the delete button
          * the row will be deleted in the updatedelete method and after the redirect
          * back to index the result set will be page 1 with a sort of id asc and limit of 2.
          */

     } elseif (($this->request->getQuery('sort') !== '')
         && is_string($this->request->getQuery('sort'))
         && !is_numeric($this->request->getQuery('sort'))
         && ($this->request->getQuery('direction') !== '')
         && is_string($this->request->getQuery('direction'))
         && !is_numeric($this->request->getQuery('direction'))) {

         /**
          * OTHER LOADS
          */
         $page = $this->request->getQuery('page');
         $sort = $this->request->getQuery('sort');
         $direction = $this->request->getQuery('direction');
         $limit = $this->request->getQuery('limit');
     }

     $query = $this->Articles->find('myArticles', [
         'user_id' => 1
     ]);

     $settings = $this->paginate = [
         'sortableFields' => [
             'id',
             'user_id',
             'title',
             'slug',
             'published',
             'created',
             'modified'
         ],
         'page' => $page,
         'sort' => $sort,
         'direction' => $direction,
         'limit' => $limit
     ];

     $articles = $this->paginate($query, $settings);
     $this->set(compact('articles'));

     $this->set('page', $page);
     $this->set('sort', $sort);
     $this->set('direction', $direction);
     $this->set('limit', $limit);
}

Updatedelete Method

public function updatedelete()
{
    $this->request->allowMethod(['post']);

    if (is_string($this->request->getData('updaterow'))
        && is_numeric($this->request->getData('updaterow'))
        && is_null($this->request->getData('deleterow'))) {

        // Update row
        $article = $this->Articles->get((int) $this->request->getData('updaterow'));
        $article->published = 0;
        if ($this->Articles->save($article)) {
            $this->Flash->success(__('The article has been updated.'));
            $page = $this->request->getData('page');
        } else {
            $this->Flash->error(__('The article could not be updated. Please, try again.'));
        }

    } elseif (is_string($this->request->getData('deleterow'))
        && is_numeric($this->request->getData('deleterow'))
        && is_null($this->request->getData('updaterow'))) {

        // Delete row
        $article = $this->Articles->get((int) $this->request->getData('deleterow'));
        if ($this->Articles->delete($article)) {
            $this->Flash->success(__('The article has been deleted.'));
            $page = null;
        } else {
            $this->Flash->error(__('The article could not be deleted. Please, try again.'));
        }

    }

    $redirect = $this->request->getQuery('redirect', [
       'action' => 'index',
        '?' => [
            'page' => $page,
            'sort' => $this->request->getData('sort'),
            'direction' => $this->request->getData('direction'),
            'limit' => $this->request->getData('limit')
        ]
    ]);

    return $this->redirect($redirect);
}

Index View

$this->Paginator->options([ // ADDED THIS
   'url' => [
        '?' => [
            'limit' => $limit
        ]
    ]
]);
?>

<div class="articles index content">
    <?= $this->Html->link(__('New Article'), ['action' => 'add'], ['class' => 'button float-right']) ?>
    <h3><?= __('Articles') ?></h3>
    <div class="table-responsive">
        <table>
            <thead>
                <tr>
                    <th><?= $this->Paginator->sort('id') ?></th>
                    <th><?= $this->Paginator->sort('user_id') ?></th>
                    <th><?= $this->Paginator->sort('title') ?></th>
                    <th><?= $this->Paginator->sort('slug') ?></th>
                    <th><?= $this->Paginator->sort('published') ?></th>
                    <th><?= $this->Paginator->sort('created') ?></th>
                    <th><?= $this->Paginator->sort('modified') ?></th>
                    <th class="actions"><?= __('Actions') ?></th>
                </tr>
            </thead>
            <tbody>
                <?php
                echo $this->Form->create(null, [ // ADDED THIS
                    'url' => ['action' => 'updatedelete']
                ]);

                foreach ($articles as $article): ?>
                <tr>
                    <td><?= $this->Number->format($article->id) ?></td>
                    <td><?= $article->has('user') ? $this->Html->link($article->user->email, ['controller' => 'Users', 'action' => 'view', $article->user->id]) : '' ?></td>
                    <td><?= h($article->title) ?></td>
                    <td><?= h($article->slug) ?></td>
                    <td><?= h($article->published) ?></td>
                    <td><?= h($article->created) ?></td>
                    <td><?= h($article->modified) ?></td>
                    <td class="actions">
                        <?= $this->Html->link(__('View'), ['action' => 'view', $article->id]) ?>
                        <?= $this->Html->link(__('Edit'), ['action' => 'edit', $article->id]) ?>
                        <?= $this->Form->button('Update', [ // ADDED THIS
                            'type' => 'submit',
                            'name' => 'updaterow',
                            'value' => $article->id
                        ]); ?>
                        <?= $this->Form->button('Delete', [ // ADDED THIS
                            'type' => 'submit',
                            'name' => 'deleterow',
                            'value' => $article->id
                        ]); ?>
                    </td>
                </tr>
                <?php endforeach;

                echo $this->Form->hidden('page', ['value' => $page]); // ADDED THIS
                echo $this->Form->hidden('sort', ['value' => $sort]); // ADDED THIS
                echo $this->Form->hidden('direction', ['value' => $direction]); // ADDED THIS
                echo $this->Form->hidden('limit', ['value' => $limit]); // ADDED THIS

            echo $this->Form->end(); // ADDED THIS
            ?>
        </tbody>
    </table>
</div>
<div class="paginator">
    <ul class="pagination">
        <?= $this->Paginator->first('<< ' . __('first')) ?>
        <?= $this->Paginator->prev('< ' . __('previous')) ?>
        <?= $this->Paginator->numbers() ?>
        <?= $this->Paginator->next(__('next') . ' >') ?>
        <?= $this->Paginator->last(__('last') . ' >>') ?>
    </ul>
    <p><?= $this->Paginator->counter(__('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')) ?></p>
    </div>
 </div>

Heres a copy/paste working example on a baked v5.1.6.


Articles Table

Custom Finder

public function findMyArticles(SelectQuery $query, $user_id): SelectQuery
{
    $query
        ->where([
        'user_id' => $user_id
    ]);

    return $query;
}

Articles Controller

Pagination Property

protected array $paginate = [
    'sortableFields' => [
        'id',
        'user_id',
        'title',
        'slug',
        'published',
        'created',
        'modified'
    ]
];

Index Method

public function index(string $menuLink = null)
{
    $menuLink = 'author';

    if (is_null($this->request->getQuery('sort'))
        && is_null($this->request->getQuery('direction'))
        && is_null($this->request->getQuery('limit'))) {

        /**
         * INITIAL LOAD
         */

        // For reason 1.
        if ($menuLink === 'author') {
            $sort = 'title';
            $direction = 'asc';
        } else {
            $sort = 'created';
            $direction = 'desc';
        }

        $redirect = $this->request->getQuery('redirect', [
            'action' => 'index',
            '?' => [
                'sort' => $sort,
                'direction' => $direction,
                'limit' => 2 // This value of 2 comes from the database in my app.
            ]
        ]);

        return $this->redirect($redirect);
        exit(0);
    }

    /**
     * OTHER LOADS
     */
    $page = $this->request->getQuery('page');
    $sort = $this->request->getQuery('sort');
    $direction = $this->request->getQuery('direction');
    $limit = $this->request->getQuery('limit');

    $query = $this->Articles->find('myArticles',
        user_id: 1
    );

    $articles = $this->paginate($query);
    $this->set(compact('articles'));

    $this->set('page', $page);
    $this->set('sort', $sort);
    $this->set('direction', $direction);
    $this->set('limit', $limit);
}

Updatedelete Method

Exactly the same as v4 example above.

Index View

Exactly the same as v4 example above except the below is removed.

$this->Paginator->options([ // ADDED THIS
   'url' => [
        '?' => [
            'limit' => $limit
        ]
    ]
]);
?>

Summary

After analysing the behaviour of the page, sort and direction in the v5 baked tutorial I noticed the following:

If the sort, direction and limit are in the url it will override these parameters in the pagination property. So all I needed to do was ensure there in the url.

I removed the sort, direction and limit from the pagination property and appended them to a redirect on initial load.