Symfony & doctrine pagination with twig

in

We can use the Doctrine\ORM\Tools\Pagination\Paginator class in doctrine to paginate results without the use of any additional libraries or bundles, here’s a quick walk through on how to paginate a result set with Symfony, Doctrine & Twig.

Creating queries

You should know how to create a query, you can use which ever method you prefer (DQL or Query Builder), say we have the following method getAllPosts() in our repository. It currently fetches all the posts from the database (e.g: 20 posts).

/**
 * We have this `getAllPosts()` function
 * We want to paginate the results using doctrines Pagination tool.
 */
public function getAllPosts()
{
    $query = $this->createQueryBuilder('p')
        ->orderBy('p.created', 'DESC')
        ->getQuery();

    $query->getResult();
}

Adding our pagination helper

We need to pass through a query object, the $currentPage (from the controller) and the $limit to the paginate() method, the offset is calculated within the paginate() method itself and the query is also performed by the Paginator() object.

View the code below:

// Place this is your repository (or where ever the query is constructed)
use Doctrine\ORM\Tools\Pagination\Paginator;

/**
 * Our new getAllPosts() method
 *
 * 1. Create & pass query to paginate method
 * 2. Paginate will return a `\Doctrine\ORM\Tools\Pagination\Paginator` object
 * 3. Return that object to the controller
 *
 * @param integer $currentPage The current page (passed from controller)
 *
 * @return \Doctrine\ORM\Tools\Pagination\Paginator
 */
public function getAllPosts($currentPage = 1)
{
    // Create our query
    $query = $this->createQueryBuilder('p')
        ->orderBy('p.created', 'DESC')
        ->getQuery();

    // No need to manually get get the result ($query->getResult())

    $paginator = $this->paginate($query, $currentPage);

    return $paginator;
}

/**
 * Paginator Helper
 *
 * Pass through a query object, current page & limit
 * the offset is calculated from the page and limit
 * returns an `Paginator` instance, which you can call the following on:
 *
 *     $paginator->getIterator()->count() # Total fetched (ie: `5` posts)
 *     $paginator->count() # Count of ALL posts (ie: `20` posts)
 *     $paginator->getIterator() # ArrayIterator
 *
 * @param Doctrine\ORM\Query $dql   DQL Query Object
 * @param integer            $page  Current page (defaults to 1)
 * @param integer            $limit The total number per page (defaults to 5)
 *
 * @return \Doctrine\ORM\Tools\Pagination\Paginator
 */
public function paginate($dql, $page = 1, $limit = 5)
{
    $paginator = new Paginator($dql);

    $paginator->getQuery()
        ->setFirstResult($limit * ($page - 1)) // Offset
        ->setMaxResults($limit); // Limit

    return $paginator;
}


Usage in your controller

In your controller you can pass the returned Paginator object through as your normally would to your view, your object has only the 5 post items within it.

/**
 * Controller Index action
 *
 * @param integer $page The current page passed via URL
 */
public function indexAction($page = 1)
{
    // ... get posts from DB...
    // Controller Action
    $posts = $repo->getAllPosts($currentPage); // Returns 5 posts out of 20

    // You can also call the count methods (check PHPDoc for `paginate()`)
    # Total fetched (ie: `5` posts)
    $totalPostsReturned = $posts->getIterator()->count()

    # Count of ALL posts (ie: `20` posts)
    $totalPosts = $posts->count()

    # ArrayIterator
    $iterator = $posts->getIterator()

    // render the view (below)
}

Rendering pagination buttons in twig

Using the above method, you will need to add pagination to your views; here is an example of rendering pagination (using Bootstrap 3) with Twig.

/**
 * Controller index action
 * We want to pass through our pagination variables
 * so we can use them to calculate the page.
 */
public function indexAction($page = 1)
{
    // ... get posts from DB...

    $limit = 5;
    $maxPages = ceil($paginator->count() / $limit);
    $thisPage = $page;
    // Pass through the 3 above variables to calculate pages in twig
    return $this->render('view.twig.html', compact('categories', 'maxPages', 'thisPage'));
}

The twig template (using Bootstrap 3) is below.

{% if maxPages > 1 %}
<ul class="pagination pagination-sm">
    {# `«` arrow  #}
    <li {{ thisPage == 1 ? 'class="disabled"' }}>
        <a href="{{ path('routeName', {page: thisPage-1 < 1 ? 1 : thisPage-1}) }}">«</a>
    </li>

    {# Render each page number #}
    {% for i in 1..maxPages %}
    <li {{ thisPage == i ? 'class="active"' }}>
    <a href="{{ path('routeName', {page: i}) }}">{{ i }}</a>
    </li>
    {% endfor %}

    {# `»` arrow #}
    <li {{ thisPage == maxPages ? 'class="disabled"' }}>
        <a href="{{ path('routeName', {page: thisPage+1 <= maxPages ? thisPage+1 : thisPage}) }}">»</a>
    </li>
</ul>
{% endif %}

Hope this helps!