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:
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.
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:
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.
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á unQuery
. 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.
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.
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">«</span>
</a>
</li>
<?php else: ?>
<li>
<span aria-hidden="true">«</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">»</span>
</a>
</li>
<?php else: ?>
<li>
<span aria-hidden="true">»</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:
$posts
es el objeto Paginator
que pasamos desde la acción en
el controlador en el contenedor de variables ViewModel
route
que se usa para generar enlaces a
páginas, enlaces a los que se puede hacer clic.¡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):
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.