How to implement a pagination for a search module in Zend Framework 2?

automatix picture automatix · Apr 16, 2013 · Viewed 6.9k times · Source

I have a module Search in my ZF2 application. The user fills in a search form out and gets a list of courses.

Now I'm adding the pagination to the module. The paginator is basically working: I can retrieve data over it and the pagination is displayed correctly (pagelinks 1-7 for 70 found courses with the dafault setting 10 items per page).

But it's still not usable. When I click on a pagelink, the form POST data is lost. I know -- it cannot work the way, how I implemented it (see the code below). But I have no idea, how to do it correctly, in order to eep checking the form data and nonetheless be able to use pagination.

That is my code:

Table class Search\Model\CourseTable

class CourseTable {

    ...

    // without pagination
    // public function findAllByCriteria(CourseSearchInput $input) {
    // with pagination
    public function findAllByCriteria(CourseSearchInput $input, $pageNumber) {
        ...
        $select = new Select();
        $where = new Where();
        $having = new Having();
        ...
        // without pagination
        // $resultSet = $this->tableGateway->selectWith($select);
        // return $resultSet;
        // with pagination
        $adapter = new \MyNamespqce\Paginator\Adapter\DbSelect($select, $this->tableGateway->getAdapter());
        $paginator = new \Zend\Paginator\Paginator($adapter);
        $paginator->setCurrentPageNumber($pageNumber);
        return $paginator;
    }

    ... 

}

Search\Controller\SearchController

class SearchController extends AbstractActionController {

    public function searchCoursesAction() {
        $form = $this->getServiceLocator()->get('Search\Form\CourseSearchForm');
        $request = $this->getRequest();
        if ($request->isPost()) {
            $courseSearchInput = new CourseSearchInput();
            $form->setInputFilter($courseSearchInput->getInputFilter());
            $form->setData($request->getPost());
            if ($form->isValid()) {
                $courseSearchInput->exchangeArray($form->getData());
                // without pagination
                // $courses = $this->getCourseTable()->findAllByCriteria($courseSearchInput);
                // with pagination
                $page = $this->params()->fromRoute('page');
                $paginator = $this->getCourseTable()->findAllByCriteria($courseSearchInput, $page);
            } else {
                $paginator = null;
            }
        } else {
            $paginator = null;
        }
        return new ViewModel(array(
            'form' => $form,
            // without pagination
            // 'courses' => $courses,
            // with pagination
            'paginator' => $paginator,
            'cities' => ...
        ));
    }

    ...

}

How to get it working?

Answer

Tahmina Khatoon picture Tahmina Khatoon · May 27, 2013

I also have the same problem, and I have solved it. But this is not good way. May be the idea will help you.

I solved it as follow: (Search pagination for Zend tutorial album module)

I build two action in controller named "search" and "index".

Whenever the search form submitted, it always post the value to search action. Search action build the url with search parameters, and redirect to index to disply search result.

And when the pagination links clicked, then posted values are passed through url. So whenever index action ask for search parameters, it always get the values in same format.

I defined route as follows:

'album' => array(
    'type'    => 'segment',
    'options' => array(
        'route'    => '/album[/:action][/:id][/page/:page][/order_by/:order_by][/:order][/search_by/:search_by]',
        'constraints' => array(
            'action'    => '(?!\bpage\b)(?!\border_by\b)(?!\bsearch_by\b)[a-zA-Z][a-zA-Z0-9_-]*',
            'id'     => '[0-9]+',
            'page' => '[0-9]+',
            'order_by' => '[a-zA-Z][a-zA-Z0-9_-]*',
            'order' => 'ASC|DESC',
        ),
        'defaults' => array(
            'controller' => 'Album\Controller\Album',
            'action'     => 'index',
        ),
    ),
),

There is a parameter named "search_by", which will keep all search parameters as a json string. This is the point, which is not good I know, but have not find any other way yet.

"Search" action build this string as -

public function searchAction()
{

    $request = $this->getRequest();

    $url = 'index';

    if ($request->isPost()) {
        $formdata    = (array) $request->getPost();
        $search_data = array();
        foreach ($formdata as $key => $value) {
            if ($key != 'submit') {
                if (!empty($value)) {
                    $search_data[$key] = $value;
                }
            }
        }
        if (!empty($search_data)) {
            $search_by = json_encode($search_data);
            $url .= '/search_by/' . $search_by;
        }
    }
    $this->redirect()->toUrl($url);
}

And next index action decode the string, do necessary action, and also send the json string to view.

public function indexAction() {
    $searchform = new AlbumSearchForm();
    $searchform->get('submit')->setValue('Search');

    $select = new Select();

    $order_by = $this->params()->fromRoute('order_by') ?
            $this->params()->fromRoute('order_by') : 'id';
    $order = $this->params()->fromRoute('order') ?
            $this->params()->fromRoute('order') : Select::ORDER_ASCENDING;
    $page = $this->params()->fromRoute('page') ? (int) $this->params()->fromRoute('page') : 1;
    $select->order($order_by . ' ' . $order);
    $search_by = $this->params()->fromRoute('search_by') ?
            $this->params()->fromRoute('search_by') : '';


    $where    = new \Zend\Db\Sql\Where();
    $formdata = array();
    if (!empty($search_by)) {
        $formdata = (array) json_decode($search_by);
        if (!empty($formdata['artist'])) {
            $where->addPredicate(
                    new \Zend\Db\Sql\Predicate\Like('artist', '%' . $formdata['artist'] . '%')
            );
        }
        if (!empty($formdata['title'])) {
            $where->addPredicate(
                    new \Zend\Db\Sql\Predicate\Like('title', '%' . $formdata['title'] . '%')
            );
        }

    }
    if (!empty($where)) {
        $select->where($where);
    }


    $album = $this->getAlbumTable()->fetchAll($select);
    $totalRecord  = $album->count();
    $itemsPerPage = 2;

    $album->current();
    $paginator = new Paginator(new paginatorIterator($album));
    $paginator->setCurrentPageNumber($page)
            ->setItemCountPerPage($itemsPerPage)
            ->setPageRange(7);

    $searchform->setData($formdata);

    return new ViewModel(array(
        'search_by'  => $search_by,
        'order_by'   => $order_by,
        'order'      => $order,
        'page'       => $page,
        'paginator'  => $paginator,
        'pageAction' => 'album',
        'form'       => $searchform,
        'totalRecord' => $totalRecord
    ));


}

All the sorting and paging url contain that string.

If you know all the searching paarameters before, then you can define that at route, and pass like the same way without json string. As I have to build a common search, I have build a single string.

Source code for "Album search" is available in git hub at https://github.com/tahmina8765/zf2_search_with_pagination_example.

Live Demo: http://zf2pagination.lifencolor.com/public/album