Paginación en Zend Framework 2

Vamos a ver como paginar registros con ZF2 esta es una herramienta muy importante dentro de un Framework. Esta librería en ZF2 puede paginar cualquier array, y como casi todas las herramientas que nos da Zend tienen varias formas de ser utilizada, unas más y otras menos complejas.

Paginar array

El paginador utiliza el adaptador ArrayAdapter para ejecutar la lógica de paginación. Lo que hace es recibir un array y lo pagina. Esta forma de paginar no es la más recomendable, para paginar datos desde una base de datos porque tendríamos que pasarle un array con todo el resulset con lo cual perdería el sentido la paginación porque seria una solución poco optima. Si tenemos 100 usuarios atacando a la misma base de datos y consultan una tabla con 100.000 registros se sacarían todos los registros de la tabla y solo se mostraran unos pocos, con lo cual el rendimiento cae mucho.

El modelo

<?php
namespace Modulo\Model\Entity;

use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Select;
use Zend\Db\ResultSet\ResultSet;


class UsuariosModel extends TableGateway{
    private $dbAdapter;
   
    public function __construct(Adapter $adapter = null, $databaseSchema = null, ResultSet $selectResultPrototype = null){
        $this->dbAdapter=$adapter;
        return parent::__construct('usuarios', $this->dbAdapter, $databaseSchema,$selectResultPrototype);
    }
	
	public function listarTodos($paginated=false){
		$consulta=$this->dbAdapter->query("SELECT * FROM usuarios",Adapter::QUERY_MODE_EXECUTE);
		$datos=$consulta->toArray();
		return $datos;
	}
     
}

El controlador

<?php
namespace Modulo\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Db\Adapter\Adapter;

//Incluir modelos
use Modulo\Model\Entity\UsuariosModel;

class CrudController extends AbstractActionController{
    private $dbAdapter;
    
    public function indexAction(){
        return new ViewModel();
    }
    
    public function paginacionAction(){
	//Conexion
        $this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter');
	
	//Cargar modelo
        $usuarios=new UsuariosModel($this->dbAdapter);
	
	//Conseguir el valor id de la url y por defecto ponerle 1
        $page=$this->params()->fromRoute("id",1);
    
	//Resulset a paginar
        $todos=$usuarios->listarTodos();
	
	/* Creamos el objeto Paginator y le pasamos como parametro 
	la instancia de otro objeto que nos permite paginar arrays */
        $paginator = new \Zend\Paginator\Paginator(new \Zend\Paginator\Adapter\ArrayAdapter($todos));
        
	/*
	Le decimos que la pagina actual sea la que le llega por la url,
	que saque dos registros por pagina y el numero de numeros que
	se van a mostrar como maximo
	*/
		$paginator->setCurrentPageNumber($page)
                ->setItemCountPerPage(2)
                ->setPageRange(7);
      
	//Renderizamos la vista
         $vista = new ViewModel(array(
             'paginator'=>$paginator
                 ));
   
        return $vista;
    }
    
}

Esto es importante, hay que tener una vista exclusiva para mostrar la navegación de la paginación y añadirla al config del modulo.

Añadimos la vista al module.config.php

Paginación con Zend Framework 2 module.config.php

Creamos la vista de la navegación de la paginación, en el directorio layout.
Numpaginacion.phtml

<div class="paginationControl">
<a href="<?php echo $this->url('crud', array('action'=>'paginacion','id' => $this->first)); ?>">Primera </a>|   
    
<!-- Link para ir a la pagina anterior -->
<?php if (isset($this->previous)){ ?>
  <a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->previous)); ?>">
    &lt; Previa
  </a> |
<?php }else{ ?>
        <span class="disabled">&lt; Previa</span> |
<?php } ?>

<!-- Links numerados -->
<?php foreach ($this->pagesInRange as $id){ ?>
  <?php if ($id != $this->current){ ?>
    <a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $id)); ?>">
        <?php echo $id; ?>
    </a> |
<?php }else{ 
     echo $id." | "; 
     } 
} ?>

<!-- Link para ir a la pagina siguiente -->
<?php if (isset($this->next)){ ?>
  <a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->next)); ?>">
    Siguiente &gt;
  </a>
<?php }else{ ?>
  <span class="disabled">Siguiente &gt;</span>
<?php } ?>
  |<a href="<?php echo $this->url('crud', array('action'=>'paginacion','id' => $this->last)); ?>"> Ultima</a>   
</div>

La vista:
Paginación.phtml

<h1>Paginación en Zend Framework 2</h1>
<table class="table">
    <tr>
        <th>ID</th>
        <th>Correo</th>
        <th>Contraseña</th>
        <th>Nombre y apellidos</th>
    </tr>
    <?php 

foreach($this->paginator as $usuario){ ?>
    <tr style="background-color:<?php echo $this->cycle(array("#F0F0F0","#FFFFFF"))->next()?>">
        <td><?=$usuario["id"]?></td>
        <td><?=$usuario["email"]?></td>
        <td><?=$usuario["password"]?></td>
        <td><?=$usuario["nombre"]." ".$usuario["apellido"]?></td>
    </tr>
    <?php  } ?>
</table>

<?php 
/*
Imprimimos la navegación, le pasamos:
	1. El paginador, el tipo de navegacion.
	2. Le pasamos el tipo de navegacion (All, Elastic, Jumping, Sliding)
	3. La ruta y la accion a la que pertenece la paginacion,
	   esto hará que se construyan bien los enlaces
*/
echo $this->paginationControl($this->paginator,'Sliding','numpaginacion', array('route' => 'crud','action'=>'paginacion')); 
?>

Iterator Adapter

De la misma forma que hemos visto en este ejemplo podemos utlizar el adaptador Iterator el cual hace prácticamente lo mismo.

Podriamos tener un metodo que nos haga la siguiente consulta.

public function listarTodos($paginated=false){
       $resultSet = $this->select(function (Select $select){
            $select->columns(array('id', 'name'));
            $select->order(array('id asc'));  
        });
         
        $resultSet->buffer();
        $resultSet->next();
          
        return $resultSet; 

	}

Y en el controlador

        $todos=$usuarios->listarTodos();
        $paginator = new \Zend\Paginator\Paginator(new \Zend\Paginator\Adapter\Iterator ($todos));

Esta forma de paginar es algo mejor que la anterior, no es necesario pasarle un array con el resultSet sino que el lo saca. Si utilizamos el “ORM” que nos dá zf2 puede ser que sea mas optima que la anterior, aunque no estoy 100% seguro.

Paginación optima con DBSelect Adapter

Esta forma de paginar es la más correcta y eficiente cuando vamos a sacar datos de una BD, ya que el paginador se encarga de hacer los límites en la consulta para sacar solamente las filas necesarias, de forma que el rendimiento aumenta. Eso sí nos obliga a utilizar las capas de abstracción para las consultas, no podemos hacer una consulta en lenguaje SQL, lo cual nos garantiza por otro lado que la consulta estará lo mas optimizada posible.

“Este enfoque probablemente no le dará una enorme ganancia de rendimiento en pequeñas colecciones y / o consultas de selección sencillas. Sin embargo, con consultas complejas y de grandes colecciones, un enfoque similar podría darle un significativo aumento del rendimiento.” By Documentación oficial de Zend Framework 2

En un método de un modelo.

public function fetchAll2($currentPage = 1, $countPerPage = 2) {
	/*Objeto select para hacer consultas complejas utilizando métodos
	  también nos serviría el objeto Sql o utilizar el método 
	  $sql = $this->getSql()->select(); para consultas simples,
  que solo nos permite utilizar la tabla que tengamos asignada la la clase actual (TableGateway) lo cual nos limita
	*/
	$select = new Select();
	
	// Montamos una consulta compleja

	//Preparamos la subconsulta
	$subquery = new Select();
        $subquery->from("siguiendo")
                ->columns(array("seguido"))
                ->where->equalTo("seguido",15)->or->equalTo("usuario", 15);
        
	//Consulta multitabla
       $select = new Select();
       $select->from(array("p"=>"publicaciones"))
       ->columns(array('*')) 
       ->join(array("u" =>'usuarios'), 'p.usuario=u.id_usuario', array("nick","nombre","apellidos","img_usu"=>"imagen"))
       ->where->in('p.usuario', $subquery);
       $select->order("p.id_publicacion DESC");
   
   /* Instanciamos en el método del modelo el paginador con el adaptador DbSelect
	  al que le pasamos el objeto select con la consulta y el adaptador de la base 
	  de datos */
	$adapter = new \Zend\Paginator\Adapter\DbSelect($select, $this->dbAdapter);
	$paginator = new \Zend\Paginator\Paginator($adapter);
	
	//Le decimos la cantidad de elementos por pagina y la pagina actual
	$paginator->setItemCountPerPage($countPerPage);
	$paginator->setCurrentPageNumber($currentPage);
	
	//Devolvemos el paginador
	return $paginator;
}

En un metodo action de un controlador.

public function pruebasAction(){
//Conseguimos el adaptador de la base de datos
$this->dbAdapter=$this->getServiceLocator()->get('Zend\Db\Adapter');

//Le pasamos al modelo el adaptador
$publicaciones=new PublicacionesModel($this->dbAdapter);

//Conseguimos el parametro id de la url
$page=$page=$this->params()->fromRoute("id",1);;
	
/* El paginador nos llega desde este metodo del modelo,
   al que le pasamos la pagina actual y el numero de registros
   por pagina
*/
$paginator=$publicaciones->fetchAll2($page, 15);
$paginator->setPageRange(7); //Se van a mostrar 7 numeros en la paginación
	
// Le pasamos a la vista el paginador con los registros
// la ruta y el metodo action en el que va a funcionar la paginacion
$view=new ViewModel(array(
	"paginator"=>$paginator,
	"route"        =>"actualizar",
	"action"       =>"pruebas"
   ));

return $view;
}

Creamos la vista de la navegación de la paginación, en el directorio layout.
Numpaginacion.phtml

<div class="paginationControl">
<a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->first)); ?>">Primera </a>|   
<!-- Previous id link -->
<?php if (isset($this->previous)){ ?>
  <a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->previous)); ?>">
    &lt; Previa
  </a> |
<?php }else{ ?>
        <span class="disabled">&lt; Previa</span> |
<?php } ?>

<!-- Numbered id links -->
<?php foreach ($this->pagesInRange as $id){ ?>
  <?php if ($id != $this->current){ ?>
    <a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $id)); ?>">
        <?php echo $id; ?>
    </a> |
<?php }else{ 
     echo $id." | "; 
     } 
} ?>

<!-- Next id link -->
<?php if (isset($this->next)){ ?>
  <a class="siguiente" href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->next)); ?>">
    Siguiente &gt;
  </a>
<?php }else{ ?>
  <span class="disabled">Siguiente &gt;</span>
<?php } ?>
  |<a href="<?php echo $this->url($this->route, array('action'=>$this->action,'id' => $this->last)); ?>"> Ultima</a>   
</div>

En la vista

<?php
foreach($this->paginator as $pub){
    echo $pub["nick"]."<br/>";
    echo $pub["texto"]."<br/>";
    echo $pub["img"]."<br/>";
    echo "<hr/>";
}

//Imprimimos el paginador recibiendo los parametros pasados desde el controlador
echo $this->paginationControl($this->paginator,'Sliding','numpaginacion', 
array('route' =>$this->route,'action'=>$this->action));
?>

Más información:
Paginador en la documentación oficial de Zend Framework 2

Víctor Robles WEB

Autor: Victor

Desarrollador web - Formador online - Blogger

Compartir este post

6 Comentarios

  1. Hola,

    estoy empezando con Zend y al intentar hacer una paginación tengo problemas con la ruta, creo que el problema se debe a la configuración del module.config, pero no se muy bien como modificarlo sin que se me caiga la aplicación. Tengo la paginacion en la ruta application/index/blog y los numeros de pagina me apuntan a application.

    Este es el archivo:

                'application' => array(
                    'type'    => 'Literal',
                    'options' => array(
                        'route'   => '/application',
                        'defaults' => array(
                            '__NAMESPACE__' => 'Application\Controller',
                            'controller'    => 'Index',
                            'action'        => 'index',
                        ),
                    ),
                    'may_terminate' => true,
                    'child_routes' => array(
                        'default' => array(
                            'type'    => 'Segment',
                            'options' => array(
                                //'route'    => '/[:controller[/:action]]',
                                'route'    => '/[:controller[/:action[/:id]]]',
                                'constraints' => array(
                                    'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                    'action'    => '[a-zA-Z][a-zA-Z0-9_-]*',
                                ),
    

    Muchas gracias y saludos
    Alberto

    Responder
    • Si me dices el error que te tira Zf2, te podría orientar un poco más, pero de entrada creo que el paginador sé que funciona seguro sobre rutas del tipo segment y que no sean rutas hijas de esta forma:

      'nombre_ruta'=>array(
                       'type'=>'Segment',
                          'options'=>array(
                                                        //acción  parametro1 parametro2
                              'route' => '/nombre_ruta[/[:action][/:id][/:id2]]',
                              'constraints' => array(
                                      'action'  =>  '[a-zA-Z][a-zA-Z0-9_-]*',
                              ), 
                              'defaults'  =>  array(
                                      'controller' => 'Modulo\Controller\Index',
                                      'action'     => 'index'
                              ),
                          ),
                 ),
      

      También revisa la vista que imprime las páginas.

      Responder
    • Bueno es muy confuso lo que explicas ya que pueden ser otros factores, mira los invokables o la ruta al invocar el modulo.

      Responder
  2. Hola,

    Gracias por responder. Voy a intentar explicarme, lo que me ocurre no es que tenga ningún error, sino son los enlaces que genera el paginador que apuntan a la home del sitio, por ejemplo, al pulsar sobre la pagina 2 me lleva a la home…
    Espero haberme explicado, sino me dices.

    Lo que no entiendo bien y la verdad le tengo bastante respeto es lo de modificar el archivo de configuracion, tengo que modificar la ruta que ya existe o agregar una nueva. Perdona el desconocimiento pero esto del routing como que no.

    Saludos y gracias
    Alberto

    Responder
  3. Solucionado, muchas gracias, agregando la ruta funcionan los enlaces, estaba empeñado en modificarla.

    Saludos

    Responder
  4. me parece bien que ayudes a la comunidad y mas con este framewok.

    Responder

Poner un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *