1. Base de datos
  2. Base de datos: Paginación

Introducción

En otros frameworks, la paginación puede ser muy tediosa. Esperamos que el enfoque de Laravel hacia la paginación sea un soplo de aire fresco. El paginador de Laravel está integrado con el constructor de consultas y Eloquent ORM y proporciona una paginación conveniente y fácil de usar de los registros de la base de datos sin configuración alguna.

Por defecto, el HTML generado por el paginador es compatible con el framework Tailwind CSS; sin embargo, también hay soporte para la paginación de Bootstrap.

Tailwind JIT

Si estás utilizando las vistas de paginación Tailwind predeterminadas de Laravel y el motor Tailwind JIT, debes asegurarte de que la clave content del archivo tailwind.config.js de tu aplicación haga referencia a las vistas de paginación de Laravel para que sus clases de Tailwind no se purguen:

content: [
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
],

Uso Básico

Paginar Resultados de Constructor de Consultas

Hay varias formas de paginar elementos. La más simple es utilizando el método paginate en el constructor de consultas o en una consulta Eloquent. El método paginate se encarga automáticamente de establecer los valores de "límite" y "desplazamiento" de la consulta según la página actual que esté viendo el usuario. Por defecto, la página actual se detecta según el valor del argumento de cadena de consulta page en la solicitud HTTP. Este valor se detecta automáticamente por Laravel y también se inserta automáticamente en los enlaces generados por el paginador.

En este ejemplo, el único argumento pasado al método paginate es la cantidad de elementos que deseas mostrar "por página". En este caso, especifiquemos que queremos mostrar 15 elementos por página:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
 
class UserController extends Controller
{
/**
* Mostrar todos los usuarios de la aplicación.
*/
public function index(): View
{
return view('user.index', [
'users' => DB::table('users')->paginate(15)
]);
}
}

Paginación Simple

El método paginate cuenta el número total de registros coincidentes con la consulta antes de recuperar los registros de la base de datos. Esto se hace para que el paginador sepa cuántas páginas de registros hay en total. Sin embargo, si no planeas mostrar el número total de páginas en la interfaz de usuario de tu aplicación, la consulta de conteo de registros es innecesaria.

Por lo tanto, si solo necesitas mostrar enlaces simples "Siguiente" y "Anterior" en la interfaz de usuario de tu aplicación, puedes usar el método simplePaginate para realizar una única y eficiente consulta:

$users = DB::table('users')->simplePaginate(15);

Paginar Resultados de Eloquent

También puedes paginar consultas Eloquent. En este ejemplo, paginaremos el modelo App\Models\User e indicaremos que planeamos mostrar 15 registros por página. Como puedes ver, la sintaxis es casi idéntica a paginar los resultados del constructor de consultas:

use App\Models\User;
 
$users = User::paginate(15);

Por supuesto, puedes llamar al método paginate después de establecer otras restricciones en la consulta, como cláusulas where:

$users = User::where('votes', '>', 100)->paginate(15);

También puedes usar el método simplePaginate al paginar modelos Eloquent:

$users = User::where('votes', '>', 100)->simplePaginate(15);

De manera similar, puedes usar el método cursorPaginate para paginar modelos Eloquent utilizando cursores:

$users = User::where('votes', '>', 100)->cursorPaginate(15);

Múltiples Instancias de Paginador por Página

A veces puede ser necesario renderizar dos paginadores separados en una sola pantalla generada por su aplicación. Sin embargo, si ambas instancias del paginador utilizan el parámetro de cadena de consulta page para almacenar la página actual, los dos paginadores entrarán en conflicto. Para resolver este conflicto, puedes pasar el nombre del parámetro de cadena de consulta que deseas usar para almacenar la página actual del paginador mediante el tercer argumento proporcionado a los métodos paginate, simplePaginate y cursorPaginate:

use App\Models\User;
 
$users = User::where('votes', '>', 100)->paginate(
$perPage = 15, $columns = ['*'], $pageName = 'users'
);

Paginación de Cursor

Mientras que paginate y simplePaginate crean consultas utilizando la cláusula SQL "offset", la paginación por cursor funciona construyendo cláusulas "where" que comparan los valores de las columnas ordenadas contenidas en la consulta, proporcionando el rendimiento de base de datos más eficiente disponible entre todos los métodos de paginación de Laravel. Este método de paginación es particularmente adecuado para conjuntos de datos grandes e interfaces de usuario de "desplazamiento infinito".

A diferencia de la paginación basada en offset, que incluye un número de página en la cadena de consulta de las URL generadas por el paginador, la paginación basada en cursor coloca una cadena de "cursor" en la cadena de consulta. El cursor es una cadena codificada que contiene la ubicación desde la cual la próxima consulta paginada debería comenzar a paginar y la dirección en la que debería paginar:

http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

Puedes crear una instancia de paginador basado en cursor mediante el método cursorPaginate ofrecido por el generador de consultas. Este método devuelve una instancia de Illuminate\Pagination\CursorPaginator:

$users = DB::table('users')->orderBy('id')->cursorPaginate(15);

Una vez que hayas obtenido una instancia de paginador basado en cursor, puedes mostrar los resultados de la paginación como lo harías normalmente al usar los métodos paginate y simplePaginate. Para obtener más información sobre los métodos de instancia ofrecidos por el paginador basado en cursor, consulta la documentación de métodos de instancia de paginador basado en cursor.

Advertencia Tu consulta debe contener una cláusula "order by" para aprovechar la paginación con cursores.

Paginación de Cursor vs. Offset

Para ilustrar las diferencias entre la paginación basada en offset y la paginación basada en cursor, examinemos algunas consultas SQL de ejemplo. Ambas consultas siguientes mostrarán la "segunda página" de resultados para una tabla users ordenada por id:

# Offset Pagination...
select * from users order by id asc limit 15 offset 15;
 
# Cursor Pagination...
select * from users where id > 15 order by id asc limit 15;

La consulta de paginación por cursor ofrece las siguientes ventajas sobre la paginación basada en offset:

  • Para conjuntos de datos grandes, la paginación por cursor ofrecerá un mejor rendimiento si las columnas "order by" están indexadas. Esto se debe a que la cláusula "offset" escanea todos los datos coincidentes previamente.
  • Para conjuntos de datos con escrituras frecuentes, la paginación basada en offset puede omitir registros o mostrar duplicados si se han agregado o eliminado resultados recientemente de la página que el usuario está viendo actualmente.

Sin embargo, la paginación por cursor tiene las siguientes limitaciones:

  • Al igual que simplePaginate, la paginación por cursor solo se puede utilizar para mostrar enlaces "Siguiente" y "Anterior" y no admite la generación de enlaces con números de página.
  • Requiere que el ordenamiento se base en al menos una columna única o una combinación de columnas que sean únicas. No se admiten columnas con valores null.
  • Las expresiones de consulta en cláusulas "order by" solo se admiten si están aliadas y se agregan a la cláusula "select" también.
  • No se admiten expresiones de consulta con parámetros.

Crear Manualmente un Paginador

En ocasiones, es posible que desees crear manualmente una instancia de paginación, pasándole una matriz de elementos que ya tienes en memoria. Puedes hacerlo creando una instancia de Illuminate\Pagination\Paginator, Illuminate\Pagination\LengthAwarePaginator o Illuminate\Pagination\CursorPaginator, según tus necesidades.

Las clases Paginator y CursorPaginator no necesitan conocer el número total de elementos en el conjunto de resultados; sin embargo, debido a esto, estas clases no tienen métodos para recuperar el índice de la última página. El LengthAwarePaginator acepta casi los mismos argumentos que el Paginator; sin embargo, requiere un recuento del número total de elementos en el conjunto de resultados.

En otras palabras, el Paginator corresponde al método simplePaginate en el generador de consultas, el CursorPaginator corresponde al método cursorPaginate, y el LengthAwarePaginator corresponde al método paginate.

Advertencia Cuando creas manualmente una instancia de paginador, debes "cortar" manualmente la matriz de resultados que pasas al paginador. Si no estás seguro de cómo hacer esto, consulta la función array_slice de PHP.

Personalizar URLs de Paginación

Por defecto, los enlaces generados por el paginador coincidirán con la URI de la solicitud actual. Sin embargo, el método withPath del paginador te permite personalizar la URI utilizada por el paginador al generar enlaces. Por ejemplo, si deseas que el paginador genere enlaces como http://example.com/admin/users?page=N, debes pasar /admin/users al método withPath:

use App\Models\User;
 
Route::get('/users', function () {
$users = User::paginate(15);
 
$users->withPath('/admin/users');
 
// ...
});

Anexar Valores de Cadena de Consulta

Puedes agregar a la cadena de consulta de los enlaces de paginación utilizando el método appends. Por ejemplo, para agregar sort=votes a cada enlace de paginación, debes realizar la siguiente llamada a appends:

use App\Models\User;
 
Route::get('/users', function () {
$users = User::paginate(15);
 
$users->appends(['sort' => 'votes']);
 
// ...
});

Puedes utilizar el método withQueryString si deseas agregar todos los valores de la cadena de consulta de la solicitud actual a los enlaces de paginación:

$users = User::paginate(15)->withQueryString();

Anexar Fragmentos de Hash

Si necesitas agregar un "fragmento de hash" a las URL generadas por el paginador, puedes utilizar el método fragment. Por ejemplo, para agregar #users al final de cada enlace de paginación, debes invocar el método fragment de la siguiente manera:

$users = User::paginate(15)->fragment('users');

Mostrar Resultados de Paginación

Al llamar al método paginate, recibirás una instancia de Illuminate\Pagination\LengthAwarePaginator, mientras que llamar al método simplePaginate devuelve una instancia de Illuminate\Pagination\Paginator. Y, finalmente, al llamar al método cursorPaginate se obtiene una instancia de Illuminate\Pagination\CursorPaginator.

Estos objetos proporcionan varios métodos que describen el conjunto de resultados. Además de estos métodos auxiliares, las instancias de paginador son iteradores y se pueden recorrer como un array. Entonces, una vez que hayas obtenido los resultados, puedes mostrarlos y renderizar los enlaces de la página usando Blade:

<div class="container">
@foreach ($users as $user)
{{ $user->name }}
@endforeach
</div>
 
{{ $users->links() }}

El método links renderizará los enlaces al resto de las páginas en el conjunto de resultados. Cada uno de estos enlaces ya contendrá la variable de cadena de consulta page adecuada. Recuerda, el HTML generado por el método links es compatible con el framework Tailwind CSS.

Ajustar la Ventana de Enlaces de Paginación

Cuando el paginador muestra enlaces de paginación, se muestra el número de página actual, así como enlaces para las tres páginas antes y después de la página actual. Usando el método onEachSide, puedes controlar cuántos enlaces adicionales se muestran en cada lado de la página actual dentro de la ventana media deslizante de enlaces generados por el paginador:

{{ $users->onEachSide(5)->links() }}

Convertir Resultados a JSON

Las clases de paginador de Laravel implementan el contrato de la interfaz Illuminate\Contracts\Support\Jsonable y exponen el método toJson, así que es muy fácil convertir los resultados de la paginación a JSON. También puedes convertir una instancia de paginador a JSON devolviéndola desde una ruta o acción del controlador:

use App\Models\User;
 
Route::get('/users', function () {
return User::paginate();
});

El JSON del paginador incluirá información adicional como total, current_page, last_page, y más. Los registros de resultados están disponibles a través de la clave data en el array JSON. Aquí tienes un ejemplo del JSON creado al devolver una instancia de paginador desde una ruta:

{
"total": 50,
"per_page": 15,
"current_page": 1,
"last_page": 4,
"first_page_url": "http://laravel.app?page=1",
"last_page_url": "http://laravel.app?page=4",
"next_page_url": "http://laravel.app?page=2",
"prev_page_url": null,
"path": "http://laravel.app",
"from": 1,
"to": 15,
"data":[
{
// Grabar...
},
{
// Grabar...
}
]
}

Personalizar la Vista de Paginación

Por defecto, las vistas renderizadas para mostrar los enlaces de paginación son compatibles con el framework Tailwind CSS. Sin embargo, si no estás utilizando Tailwind, eres libre de definir tus propias vistas para renderizar estos enlaces. Al llamar al método links en una instancia de paginador, puedes pasar el nombre de la vista como primer argumento al método:

{{ $paginator->links('view.name') }}
 
<!-- Passing additional data to the view... -->
{{ $paginator->links('view.name', ['foo' => 'bar']) }}

Sin embargo, la forma más fácil de personalizar las vistas de paginación es exportándolas a tu directorio resources/views/vendor mediante el comando vendor:publish:

php artisan vendor:publish --tag=laravel-pagination

Este comando colocará las vistas en el directorio resources/views/vendor/pagination de tu aplicación. El archivo tailwind.blade.php dentro de este directorio corresponde a la vista de paginación predeterminada. Puedes editar este archivo para modificar el HTML de paginación.

Si deseas designar un archivo diferente como la vista de paginación predeterminada, puedes invocar los métodos defaultView y defaultSimpleView del paginador dentro del método boot de tu clase App\Providers\AppServiceProvider:

<?php
 
namespace App\Providers;
 
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Iniciar cualquier servicio de la aplicación.
*/
public function boot(): void
{
Paginator::defaultView('view-name');
 
Paginator::defaultSimpleView('view-name');
}
}

Usar Bootstrap

Laravel incluye vistas de paginación construidas con Bootstrap CSS. Para usar estas vistas en lugar de las vistas de Tailwind predeterminadas, puedes llamar a los métodos useBootstrapFour o useBootstrapFive del paginador dentro del método boot de tu clase App\Providers\AppServiceProvider:

use Illuminate\Pagination\Paginator;
 
/**
* Iniciar cualquier servicio de la aplicación.
*/
public function boot(): void
{
Paginator::useBootstrapFive();
Paginator::useBootstrapFour();
}

Métodos de Instancia de Paginator / LengthAwarePaginator

Cada instancia de paginador proporciona información adicional de paginación a través de los siguientes métodos:

Método Descripción
$paginator->count() Obtén el número de elementos para la página actual.
$paginator->currentPage() Obtén el número de página actual.
$paginator->firstItem() Obtén el número de resultado del primer elemento en los resultados.
$paginator->getOptions() Obtén las opciones del paginador.
$paginator->getUrlRange($start, $end) Crea un rango de URLs de paginación.
$paginator->hasPages() Determina si hay suficientes elementos para dividir en varias páginas.
$paginator->hasMorePages() Determina si hay más elementos en el almacén de datos.
$paginator->items() Obtén los elementos para la página actual.
$paginator->lastItem() Obtén el número de resultado del último elemento en los resultados.
$paginator->lastPage() Obtén el número de página de la última página disponible. (No disponible al usar simplePaginate).
$paginator->nextPageUrl() Obtén la URL de la siguiente página.
$paginator->onFirstPage() Determina si el paginador está en la primera página.
$paginator->perPage() El número de elementos a mostrar por página.
$paginator->previousPageUrl() Obtén la URL de la página anterior.
$paginator->total() Determina el número total de elementos coincidentes en el almacén de datos. (No disponible al usar simplePaginate).
$paginator->url($page) Obtén la URL para un número de página dado.
$paginator->getPageName() Obtén la variable de cadena de consulta utilizada para almacenar la página.
$paginator->setPageName($name) Establece la variable de cadena de consulta utilizada para almacenar la página.
$paginator->through($callback) Transforma cada elemento usando un callback.

Métodos de Instancia de Cursor Paginator

Cada instancia de paginador basado en cursor proporciona información adicional de paginación a través de los siguientes métodos:

Método Descripción
$paginator->count() Obtén el número de elementos para la página actual.
$paginator->cursor() Obtén la instancia actual del cursor.
$paginator->getOptions() Obtén las opciones del paginador.
$paginator->hasPages() Determina si hay suficientes elementos para dividir en varias páginas.
$paginator->hasMorePages() Determina si hay más elementos en el almacén de datos.
$paginator->getCursorName() Obtén la variable de cadena de consulta utilizada para almacenar el cursor.
$paginator->items() Obtén los elementos para la página actual.
$paginator->nextCursor() Obtén la instancia de cursor para el siguiente conjunto de elementos.
$paginator->nextPageUrl() Obtén la URL de la siguiente página.
$paginator->onFirstPage() Determina si el paginador está en la primera página.
$paginator->onLastPage() Determina si el paginador está en la última página.
$paginator->perPage() El número de elementos a mostrar por página.
$paginator->previousCursor() Obtén la instancia de cursor para el conjunto anterior de elementos.
$paginator->previousPageUrl() Obtén la URL de la página anterior.
$paginator->setCursorName() Establece la variable de cadena de consulta utilizada para almacenar el cursor.
$paginator->url($cursor) Obtén la URL para una instancia de cursor dada.