A free and open-source book on ZF3 for beginners

Translation into this language is not yet finished. You can help this project by translating the chapters and contributing your changes.

12.15. Implementar Paginación

Por ahora tenemos solo algunas publicaciones en nuestra página Posts. Pero si suponemos que existen muchas (cientos de) publicaciones. La página será cargada muy lentamente. Además, es muy inconveniente desplazarse por la página para leer todos las publicaciones. En esta situación la paginación será de ayuda.

La paginación es cuando dividimos nuestros resultados en páginas y navegamos a través de ellas haciendo clic en el número de página en el widget de paginación.

Un ejemplo de un widget de paginación se presenta abajo, este widget usa los estilos de Twitter Bootstrap:

Figure 12.12. Ejemplo del widget de Paginación Figure 12.12. Ejemplo del widget de Paginación

12.15.1. El Paginador de Doctrine ORM

El componente Doctrine\ORM provee una clase para la paginación llamada Paginator que se encuentra en el namespace Doctrine\ORM\Tools\Pagination. Este puede tomar un objeto Query de Doctrine como entrada y, además, provee varios métodos para traer los resultados paginados (no discutiremos sobre estos métodos aquí por simplicidad). Sin embargo, el módulo Doctrine ORM no provee ningún ayudante de vista (view helpers) para generar el widget de paginación. Por esta razón usaremos la función de paginación provista por Zend Framework 3.

Aunque planeamos usar el componente paginador de ZF3 usaremos internamente el paginador de Doctrine ORM para consumir los datos. El paginador de ZF3 será solo un envoltorio para el paginador de Doctrine ORM.

12.15.2. El Paginador de ZF3

En Zend Framework 3 la paginación se implementa en el componente Zend\Paginator. Si no tenemos instalador este componente lo podemos instalar con el siguiente comando:

php composer.phar require zendframework/zend-paginator

El componente Zend\Paginator provee la clase Paginator. Sus métodos más útiles se muestran en la tabla 12.6:

Table 12.6. Métodos de la Clase Paginator de ZF3
Método Descripción
setDefaultScrollingStyle($scrollingStyle = 'Sliding') Coloca el estilo de la barra de desplazamiento.
setDefaultItemCountPerPage($count) Coloca el máximo de resultados por página.
setCurrentPageNumber($pageNumber) Coloca el número actual de la página.
count() Regresa el número de páginas.
getTotalItemCount() Regresa el número total de resultados.
getCurrentItems() Regresa los elementos de la página actual.

La clase Paginator es muy genérica y no conoce los datos del modelo subyacente por lo que necesitaremos un adaptador que provea los datos al paginador. El módulo DoctrineORMModule provee este adaptador (la clase DoctrineORMModule\Paginator\Adapter\DoctrinePaginator) que podemos usar para traer los datos desde el paginador ORM y pasarlos al paginador de ZF3.

Por ejemplo, asumiendo que tenemos un Query con un DQL que selecciona todas las publicaciones. Para paginar estos resultados podemos usar el siguiente código:

<?php
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as DoctrineAdapter;
use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
use Zend\Paginator\Paginator;

// Create ZF3 paginator.
$adapter = new DoctrineAdapter(new ORMPaginator($query, false));
$paginator = new Paginator($adapter);

// Set page number and page size.
$paginator->setDefaultItemCountPerPage(10);
$paginator->setCurrentPageNumber(1);

// Walk through the current page of results.
foreach ($paginator as $post) {
    // Do something with the current post.
}

Ahora vamos a aplicar este ejemplo en nuestra aplicación Blog. Queremos mostrar las publicaciones paginadas en la página principal de sitio web.

12.15.2.1. Modificar el PostRepository

Primero que todo, tendremos que modificar ligeramente la manera como traemos la lista de publicaciones. Anteriormente usamos el método findBy() del EntityRepository, que regresa un arreglo con todas las publicaciones. Pero tendremos que hacer que nuestro clase repositorio desarrollada a la medida PostRepository regrese un objeto y no un arreglo de publicaciones, porque el paginador de Doctrine ORM toma un objeto Query como entrada.

Agregamos el siguiente método a la clase PostRepository:

public function findPublishedPosts()
{
    $entityManager = $this->getEntityManager();

    $queryBuilder = $entityManager->createQueryBuilder();

    $queryBuilder->select('p')
        ->from(Post::class, 'p')
        ->where('p.status = ?1')
        ->orderBy('p.dateCreated', 'DESC')
        ->setParameter('1', Post::STATUS_PUBLISHED);

    return $queryBuilder->getQuery();
}

Y cambiamos el método findPostsByTag() porque queremos que también regrese un objeto Query en lugar de un arreglo:

public function findPostsByTag($tagName)
{
    $entityManager = $this->getEntityManager();

    $queryBuilder = $entityManager->createQueryBuilder();

    $queryBuilder->select('p')
        ->from(Post::class, 'p')
        ->join('p.tags', 't')
        ->where('p.status = ?1')
        ->andWhere('t.name = ?2')
        ->orderBy('p.dateCreated', 'DESC')
        ->setParameter('1', Post::STATUS_PUBLISHED)
        ->setParameter('2', $tagName);

    return $queryBuilder->getQuery();
}

Además, necesitaremos modificar ligeramente el método PostManager::getTagCloud() porque este espera un arreglo de publicaciones pero ahora recibirá un Query. Es una simple y obvia modificación por lo que no describimos como hacerla, además, podemos ver el código completo en el Blog de ejemplo.

12.15.2.2. Modificar el IndexController

Luego modificamos el método indexAction() del IndexController de la siguiente manera:

<?php
namespace Application\Controller;

// Add aliases for paginator classes
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as DoctrineAdapter;
use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
use Zend\Paginator\Paginator;
// ...

class IndexController extends AbstractActionController
{
    // ...

    public function indexAction()
    {
        $page = $this->params()->fromQuery('page', 1);
        $tagFilter = $this->params()->fromQuery('tag', null);

        if ($tagFilter) {

            // Filter posts by tag
            $query = $this->entityManager->getRepository(Post::class)
                    ->findPostsByTag($tagFilter);

        } else {
            // Get recent posts
            $query = $this->entityManager->getRepository(Post::class)
                    ->findPublishedPosts();
        }

        $adapter = new DoctrineAdapter(new ORMPaginator($query, false));
        $paginator = new Paginator($adapter);
        $paginator->setDefaultItemCountPerPage(1);
        $paginator->setCurrentPageNumber($page);

        // Get popular tags.
        $tagCloud = $this->postManager->getTagCloud();

        // Render the view template.
        return new ViewModel([
            'posts' => $paginator,
            'postManager' => $this->postManager,
            'tagCloud' => $tagCloud
        ]);
    }
}

Como podemos ver en la línea 16 capturamos la página actual page desde los parámetros GET. De esta manera podemos colocar la página de resultados ingresando la siguiente URL en el navegador web: http://localhost/application/index?page=<page>. La página por defecto es la 1.

En la línea 22 y 27 recuperamos el objeto Query desde nuestro PostRepository en lugar de un arreglo con las publicaciones. Luego en la línea 31 pasamos el objeto Query al paginador.

En las líneas 33-34 colocamos la página actual y el tamaño del paginador.

En la línea 41 pasamos el paginador en lugar del arreglo con las publicaciones para imprimir en la plantilla de vista.

12.15.2.3. Visualizar el Widget de Paginación

Y lo último pero no menos importante de nuestro trabajo. Necesitamos mostrar el widget de paginación en nuestra plantilla de vista.

Para hacer esto con el bonito aspecto de Bootstrap necesitamos primero crear una plantilla de vista parcial view/application/partial/paginator.phtml y colocar el siguiente código dentro del archivo:

<?php if ($this->pageCount): ?>

<nav>
<ul class="pagination">

<!-- Previous page link -->
<?php if (isset($this->previous)): ?>
    <li>
      <a href="<?= $this->url($this->route, [], ['query'=>['page'=>$this->previous]]); ?>" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
<?php else: ?>
    <li>
        <span aria-hidden="true">&laquo;</span>
    </li>
<?php endif; ?>

<!-- Numbered page links -->
<?php foreach ($this->pagesInRange as $page): ?>
  <?php if ($page != $this->current): ?>
    <li>
        <a href="<?= $this->url($this->route, [], ['query'=>['page'=>$page]]); ?>"><?= $this->escapeHtml($page); ?></a>
    </li>
  <?php else: ?>
    <li>
        <span aria-hidden="true"><b><?= $this->escapeHtml($page); ?></b></span>
    </li>
  <?php endif; ?>
<?php endforeach; ?>

<!-- Next page link -->
<?php if (isset($this->next)): ?>
    <li>
      <a href="<?php echo $this->url($this->route, [], ['query'=>['page'=>$this->next]]); ?>" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
<?php else: ?>
    <li>
        <span aria-hidden="true">&raquo;</span>
    </li>
<?php endif; ?>
</ul>
</nav>
<?php endif; ?>

La plantilla de vista parcial será usada por el ayudante de vista (view helper) de ZF3 para visualizar el widget de paginación. Como podemos ver, esta plantilla de vista parcial toma varias variables como entrada ($pageCount, pagesInRange, $current, $previous, $next, $route) que se usan para controlar como el widget se verá.

Vamos a mostrar el widget en nuestra plantilla de vista view/application/index/index.phtml debajo de la lista de publicaciones de la siguiente manera:

<?= $this->paginationControl($posts,
            'Sliding',
            'application/partial/paginator',
            ['route' => 'application']); ?>

El ayudante de vista PaginationControl acepta cuantro argumentos:

12.15.2.4. Resultados

¡hurra! nuestro aplicación esta lista. La página principal de nuestro sitio web contiene un hermoso widget de paginación (ver la figura 12.13):

Figure 12.13. La Página Principal con el Paginador Figure 12.13. La Página Principal con el Paginador

En este capítulo implementamos solo una paginación simple. En sitios web reales, además de la paginación, podemos querer ordenar interactivamente los resultados por determinados campos. Para tabular datos más complejas será mejor considerar el uso del complemento de jQuery DataTables or BootGrid.


Top