Paginación perfecta con PHP

Desde hace meses atrás quería compartir con ustedes un código PHP de paginación de consultas MySQL, como renovando este viejo script. Pero acabo de leer un reciente artículo publicado en sitepoint y me ha fascinado, así que he decidido traducirlo, a mi manera por supuesto. Espero les agrade, ya que publico estos tutoriales después de tiempo. El artículo se titula en inglés Perfect PHP Pagination.

Paginación perfecta con PHP

La paginación es un tema que ha sido tratado hasta el cansancio. Docenas de artículos y documentos se pueden encontrar sobre ello; pero (y ustedes saben que hay un “pero”) no estamos completamente satisfechos con las soluciones que tenemos... Hasta ahora. En este artículo les mostraremos nuestra propuesta, una mejor alternativa.

Algunas clases de paginación requieren parámetros tales como acceso a una base de datos y una o dos cadenas SQL, que luego son pasadas al constructor. Las clases que utilizan este enfoque carecen de utilidad y flexibilidad, ¿Y si queremos cambiar el formato de los números en la parte superior o inferior, por ejemplo? ¿Habrá que modificar la función de salida, o la subclase de la clase principal, sólo para anular un método? Estas potenciales soluciones son muy restrictivas y no propician la reutilización del código.

Este tutorial es un intento para crear una clase más abstracta para el manejo de la paginación de resultados. Eliminando su dependencia a las conexiones a la BD y los strings SQL. El enfoque que trataremos le proveerá una mayor flexibilidad, lo que permitirá luego a cada desarrollador, darle el diseño propio a la paginación, bastando solamente la utilización de la clase a través del patrón de diseño orientado a objetos, conocido como Estrategia Patrón de diseño...

Desde hace meses atrás quería compartir con ustedes un código PHP de paginación de consultas MySQL, como renovando este viejo script. Pero acabo de leer un reciente artículo publicado en sitepoint y me ha fascinado, así que he decidido traducirlo, a mi manera por supuesto. Espero les agrade, ya que publico estos tutoriales después de tiempo. El artículo se titula en inglés Perfect PHP Pagination.

Paginación perfecta con PHP

La paginación es un tema que ha sido tratado hasta el cansancio. Docenas de artículos y documentos se pueden encontrar sobre ello; pero (y ustedes saben que hay un “pero”) no estamos completamente satisfechos con las soluciones que tenemos... Hasta ahora. En este artículo les mostraremos nuestra propuesta, una mejor alternativa.

Algunas clases de paginación requieren parámetros tales como acceso a una base de datos y una o dos cadenas SQL, que luego son pasadas al constructor. Las clases que utilizan este enfoque carecen de utilidad y flexibilidad, ¿Y si queremos cambiar el formato de los números en la parte superior o inferior, por ejemplo? ¿Habrá que modificar la función de salida, o la subclase de la clase principal, sólo para anular un método? Estas potenciales soluciones son muy restrictivas y no propician la reutilización del código.

Este tutorial es un intento para crear una clase más abstracta para el manejo de la paginación de resultados. Eliminando su dependencia a las conexiones a la BD y los strings SQL. El enfoque que trataremos le proveerá una mayor flexibilidad, lo que permitirá luego a cada desarrollador, darle el diseño propio a la paginación, bastando solamente la utilización de la clase a través del patrón de diseño orientado a objetos, conocido como Estrategia Patrón de diseño.

¿Qué es la Estrategia Patrón de diseño?

Imagina lo siguiente: Tú tienes en tu sitio un puñado de páginas web, por lo cual debes paginar las consultas. Tu sitio utiliza una función o una clase que se encarga de recuperar las consultas y de publicarlas con los enlaces de la paginación.

Esto está muy bien, hasta que decides cambiar el layout de los enlaces de la paginación en una o en todas las páginas de los resultados. Al hacerlo, lo más probable es que tengamos que cambiar el método al que le delegó esta responsabilidad.

Una mejor solución sería crear tantos layouts como desees, y elegir dinámicamente el que deseamos se muestre en tiempo de ejecución. La Estrategia Patrón de diseño nos permite hacer esto. En pocas palabras, la estrategia de diseño de patrones es un patrón de diseño, orientado a objetos, utilizados por una clase que quiere cambiar su comportamiento en tiempo de ejecución.

Utilizando las capacidades polimórficas de PHP, una clase contenedora de parámetros (como la que crearemos en este artículo) utiliza un objeto que implementa una interfaz y que define implementaciones concretas de los métodos que se definen en la interfaz.

Mientras que una interfaz no puede ser instanciada, la podemos referenciar implementando clases. Así, cuando nosotros creamos un nuevo layout, podemos dejar que la Estrategia o la Interfaz con el contenedor (la clase de paginación) instancien los layouts dinámicamente en tiempo de ejecución. Las llamadas que producen los enlaces paginados producirán una página acorde al layout instanciado.

Archivos requeridos

Como he mencionado, este tutorial no trata sobre los mecanismos de cómo los resultados son paginados, pero sí de cómo utilizar una interfaz para implementar esta lógica sin restringir su flexibilidad. Propongo como punto de partida una clase que contiene las funcionalidades para el registro de arrays primitivos u objetos - la clase de paginación -, así como una interfaz que todos los layouts de las páginas deben implementar (PageLayout) y una implementación para el diseño de una página (DoubleBarLayout). Además, todo el código que empleemos en el desarrollo de este artículo esta disponible para descargar.

Un ejemplo básico

Los siguientes ejemplos utilizan un array de strings. Aquí están los datos:

  • Andrew
  • Bernard
  • Castello
  • Dennis
  • Ernie
  • Frank
  • Greg
  • Henry
  • Isac
  • Jax
  • Kester
  • Leonard
  • Matthew
  • Nigel
  • Oscar

Sin embargo, este código puede ser fácilmente extendido para utilizarlo en un array de índices numéricos, caracteres u otros objetos que han sido asociados previamente de una base de datos.

Así es como utilizaremos la clase Paginated:

<?php
require_once "Paginated.php";
//create an array of names in alphabetic order
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", Frank", Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar");
$pagedResults = new Paginated($names, 10, 1);
echo "<ul>";
while($row = $pagedResults->fetchPagedRow()) {
echo "<li>{$row}</li>";
}
echo "</ul>";
?>

Primero incluimos la clase Paginated e instanciamos un array con el constructor dando tres parámetros, dos de los cuales, los últimos, son opcionales.

  1. El primer parámetro es el array que mostraremos. Como hemos mencionado, estos datos pueden ser simples textos u objetos de los más complejos.
  2. El segundo parámetro es el número de resultados que queremos mostrar en una página. Por defecto se mostrarán 10 resultados.
  3. El tercer parámetro es el número de la página actual.

En el ejemplo anterior, hemos utilizado la constante 1 para especificar “Page 1”, sin embargo probablemente querrás pasar esto como un parámetro de la cadena de consulta (más detalles luego). Si una página inválida es dada al constructor, entonces se mostrará la página 1 por defecto.

Llamando al método fetchPagedRow desde dentro del bucle While, nuestro código itera a través del array, imprimiendo los primeros diez nombres de la lista (en el ejemplo, "Kester", "Leonard", "Matthew", "Nigel" y "Oscar" son omitidos). Estos nombres serán mostrados en la segunda página, pero como la imagen de abajo lo ilustra, no hay enlace a la página 2 aún. Aunque nuestra clase Paginated gestiona el acceso a cualquier objeto registrado por el programador, la responsabilidad de mostrar los enlaces de la paginación son delegados a una clase que implementa la interfaz PageLayout.

Paginación PHP

Vamos a añadir algo de código para mostrar los números de página, a continuación nos pondremos a profundizar un poco más en las funcionalidades de esta clase.

Crea un archivo nuevo de extensión PHP que contenga el siguiente código:

<?php
require_once "Paginated.php";
require_once "DoubleBarLayout.php";
//create an array of names in alphabetic order
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", "Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar");
$page = $_GET['page'];
$pagedResults = new Paginated($names, 10, 1);
echo "<ul>";
while($row = $pagedResults->fetchPagedRow()) {
echo "<li>{$row}</li>";
}
echo "</ul>";
$pagedResults->setLayout(new DoubleBarLayout());
echo $pagedResults->fetchPagedNavigation();
?>

Cuando miramos la secuencia de comandos ahora, veremos una lista con los diez primeros nombres, así como algunos datos adicionales de orientación que se muestran en la imagen siguiente. Nuestro script ahora muestra el texto “Page 1”, así como un enlace a la segunda página que dice "next >".

Paginación perfecta con PHP y MySQL

En el snippet anterior hemos hecho uso de una clase llamada DoubleBarLayout, que implemente la interfaz PageLayout y que contenga una implementación del método fetchPagedLinks. Este método toma dos parámetros que queremos sean añadidos a los enlaces (dado el caso).

Lo bueno de este método es que se aprovecha de las capacidades polimórficas de PHP, permitiendo la estrategia que este previamente registrado para ser llamado. Por lo tanto, es importante para nosotros, establecer la estrategia en primer lugar, antes de llamar al método. Establecer la estrategia se logra a través de llamada al método setLayout, el cual toma como parámetro un objeto que implemente la interfaz de PageLayout.

Coloca más de uno de estos enlaces y verá que el parámetro de la página y su valor de 2 está incluido en la URL del número de página. Sin embargo, en el estado actual, si se hace clic en el enlace a la segunda página, los nombres que esperamos no serán mostrados.

Esto es así, por el contructor de Paginated.

Este constructor toma 3 parametros.

  1. El array de variables primitivas u objetos que serán procesadas
  2. El número de registros que se mostrarán
  3. El número de página

Debido a que el método fetchPagedNavigation escribe un parámetro de consulta, nosotros podemos sustituir nuestro código duro de “1” por el valor de una variable $_GET['page']. De esta manera, si el usuario modifica el valor manualmente en la URL a algo que no sea válido, automáticamente Paginated colocará por defecto la página 1. ¿Cómo usted logra validar el parámetro GET? Es algo a gusto, por lo que no nos extenderemos en este punto.

Flexibilidad en los esquemas del diseño de páginas

La flexibilidad de esta clase se logra a través de la interfaz de PageLayout, la cual es parte del objeto Paginated. La interfaz de PageLayout puede referenciar cualquier objeto que lo implemente, y llamar al método Paginated fetchPagedNavigation hará que el objeto actualmente registrado sea instanciado. Si antes no haz utilizado interfaces, esto puede parecerte un poco confuso, pero, el resultado final, es que el código correcto será llamado y los resultados se distribuirán correctamente a lo largo de las páginas.

Para implementar esta técnica, todo lo que necesitas es crear un layout Estrategia que implemente la interfaz de PageLayout . Y luego, proveer una aplicación para el método fetchPagedLinks.

Este método tiene dos parámetros:

  1. $parent, que es el objeto Paginated
  2. $queryVars, que es la lista de parámetros de consulta para anexar a los números de página (opcional).

Hay tres puntos importantes a notar aquí:

Usted nunca deberá hacer llamadas directas a fetchPagedLinks; todos los métodos de Paginated pueden ser accedidos a través del objeto padre.

Si deseas utilizar tu propio layout, debes cambiar el diseño del resultado paginado a través de las llamadas a setLayout.

Con estos puntos en mente, vamos a crear nuestro propio diseño de páginas.

 Vamos a llamarlo TrailingLayout. Aquí está el código:

<?php
class TrailingLayout implements PageLayout {
public function fetchPagedLinks($parent, $queryVars) {
$currentPage = $parent->getPageNumber();
$totalPages = $parent->fetchNumberPages();
$str = "";
if($totalPages >= 1) {
for($i = 1; $i <= $totalPages; $i++) {
$str .= " <a href=\"?page={$i}$queryVars\">Page $i</a>";
$str .= $i != $totalPages ? " | " : "";
}
}
return $str;
}
}
?>

La clase anterior, TrailingLayout, implementa la interfaz PageLayout y provee la implementación para fetchPagedLinks. Recuerda que el parámetro $parent es una instancia del objeto Paginated, de modo que podemos determinar la página actual, y el número total de páginas, por la realización de llamadas a getPageNumber y fetchNumberPages, respectivamente.

En este simple diseño, una vez que hay más de una sola página, el script hará bucle a través del array de páginas, y creará un enlace y número de página para cada una. Las $queryVars también son escritas en el href como parte del bucle, el parámetro $queryVars resulta muy útil cuando se está paginando búsquedas. Los resultados de una búsqueda pueden haber incluido algunos pocos parámetros. Ten en cuenta que el string “Page” no es parte de las queryVars, pero está escrita por el bucle que añade los números de página del string.

Ahora vamos a probar la aplicación del nuevo diseño:

<?php
require_once "Paginated.php";
//include your customized layout
require_once "TrailingLayout.php";
//create an array of names in alphabetic order. A database call could have retrieved these items
$names = array("Andrew", "Bernard", "Castello", "Dennis", "Ernie", "Frank", "Greg", "Henry", "Isac", "Jax", "Kester", "Leonard", "Matthew", "Nigel", "Oscar");
$page = $_GET['page'];
$pagedResults = new Paginated($names, 10, $page);
echo "<ul>";
while($row = $pagedResults->fetchPagedRow()) {
echo "<li>{$row}</li>";
}
echo "</ul>";
//$pagedResults->setLayout(new TrailingLayout());
echo $pagedResults->fetchPagedNavigation("&firstLetter=l");
?>

Si ejecutáramos el script tal cual esta, nos daría el siguiente error.

"Fatal error: Call to a member function fetchPagedLinks() on a non-object".

Este error se produce porque aún no se ha registrado la estrategia que se desea utilizar antes de llamar a fetchPagedNavigation. Para cambiar el diseño de los enlaces de página, se pasa al método setLayout un parámetro, que puede ser cualquier objeto que implemente la interfaz de PageLayout. En el código de nuestro ejemplo anterior TrailingLayout, hay que descomentar la penúltima línea de código PHP, y actualizar la página para ver el resultado final, y que se muestra a continuación.

Paginación con PHP

La última línea de este código demuestra que el método fetchPagedNavigation puede tomar como parámetro opcional el string para definir el resto de la consulta (note la inclusión del ampersand antes de firstLetter del parámetro en la URL para distinguirlo del parámetro de la página.

En resumen

En este artículo hemos visto la Estrategia de Diseño de Patrones, que se puede utilizar cuando buscamos dar flexibilidad, especialmente cuando vayamos a editar los enlaces paginados.

Hemos visto este patrón en acción a través de la clase Paginated, que esperamos te resulte útil, cuando este mostrando sus datos en múltiples páginas. La clase se puede utilizar para mostrar arrays que contienen datos primitivos u objetos mucho más complejos.

Hola que tal?? bueno lo

Hola que tal?? bueno lo primero felicitarte por el gran articulo y mi consulta del millón si esta paginación funciona solamente con mysql o es independiente de la base de datos

Saludos..

Enviado por Victor (no verificado) el Sáb, 05/17/2008 - 12:33.
Es independiente, en el

Es independiente, en el ejemplo esta con un array. Lo importante es que proporciones los datos pedidos.

Enviado por baluart el Sáb, 05/17/2008 - 12:58.
Gracias, que buen

Gracias, que buen aporte

------------------------------------
Diseño Web
Diseño Web Peru
desarrollo de software

Enviado por Joel Cristobal (no verificado) el Sáb, 06/28/2008 - 15:37.
soy novato en esto y no puedo

soy novato en esto y no puedo ver el resultado q es lo que me falta hacer...escribo el nombre.php y ejecuto...

Enviado por cgi (no verificado) el Lun, 06/30/2008 - 21:28.
me sirve esta muy entendible

me sirve esta muy entendible lo voy a probar a ver que tal.

Enviado por REx (no verificado) el Lun, 07/07/2008 - 14:53.
Trabajo con MySql y PHP. y

Trabajo con MySql y PHP. y necesito una paginacion muy buena, por que mis reportes son demasiado extensos pero no lo digo por el # de paginas si no en la informacion de cada consulta. pero la verdad no me funciona no se por que sera si me puedes colaborar te lo agradeceria..

Enviado por Oscar Diaz (no verificado) el Vie, 08/15/2008 - 18:26.
que buen post, lo veo super

que buen post, lo veo super util cuando tenga un numero regular de registro pero que sucederia si tengo que publicar mas de 500 registros paginando paginas de 50 en 50, cargaria el array con los 500 registros, no estaria abarcando mucha memoria ?? una de las bondades de la paginacion es no tener que leer todos los registros sino ir recorriendolos parte por parte no es asi ?

Enviado por cfrasan (no verificado) el Mar, 09/16/2008 - 00:04.
Todo muy lindo, pero utilizar

Todo muy lindo, pero utilizar este script con consultas sql implica recorrer dos veces un array ... demorando el doble del tiempo siempre y cuando el array no sea demasiado grande.

Enviado por Anonymous (no verificado) el Lun, 09/29/2008 - 21:31.
he bajado el codigo fuente de

he bajado el codigo fuente de la pagina y aparece un error :
Parse error: parse error, expecting `T_OLD_FUNCTION' or `T_FUNCTION' or `T_VAR' or `'}'' in C:\Archivos de programa\Apache Group\Apache2\htdocs\pruebas\paginated-demo\src\Paginated.php on line 12

la verdad nose de k es el error:

Enviado por galax (no verificado) el Mar, 11/11/2008 - 13:52.
Excelente me ha servido de

Excelente me ha servido de mucho ;-)

Enviado por Invest (no verificado) el Jue, 04/02/2009 - 17:41.
Bueno el artículo, he

Bueno el artículo, he probrado el scrip, PERO HAY UN SCRIPT MUCHO MEJOR Y MUCHO MAS FÁCIL SE LLAMA PAGINATOR.PHP y les puedo asegurar que es mucho mas sencillo que cualquier otro que se lo presente, es open source, puedes alimentar con los cambios que quieras pero al decir verdad con lo que ya tiene es mas que suficiente... http://jpinedo.webcindario.com/scripts/paginator/ No te defraudará y es Gratis

Enviado por sabio (no verificado) el Mar, 05/12/2009 - 06:33.
Hola, hay manera de integrar

Hola, hay manera de integrar este código a wordpress, `puesto que el plugin de wp-pagenavi, solo paginas las entradas, no las páginas estáticas. Muchas gracias
Si es posible hacerlo, me indicas cómo?

Enviado por jhonattan (no verificado) el Lun, 08/31/2009 - 22:09.
Lo mejor es crear un plugin

Lo mejor es crear un plugin para wordpress, guiate con pagenavi, fijate como lo han programado y sigue las instrucciones que ofrece wordpress. Por otra parte, lee esto

http://wordpress.org/support/topic/291703

Ojalá te sirva

Enviado por baluart el Mar, 09/01/2009 - 02:09.

Deja tu comentario

El contenido de este campo se mantiene privado y no se mostrará públicamente.
  • Las direcciones de las páginas web y las de correo se convierten en enlaces automáticamente.
  • Etiquetas HTML permitidas: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Saltos automáticos de líneas y de párrafos.

Más información sobre opciones de formato