1. База данных
  2. База данных: Пагинация

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

В других фреймворках пагинация может быть очень трудной. Мы надеемся, что подход Laravel к пагинации будет свежим взглядом. Пагинатор Laravel интегрирован с построителем запросов и ORM Eloquent и предоставляет удобную, простую в использовании пагинацию записей в базе данных без какой-либо конфигурации.

По умолчанию HTML, созданный пагинатором, совместим с фреймворком Tailwind CSS, однако также поддерживается пагинация Bootstrap.

Tailwind JIT

Если вы используете представления пагинации Tailwind по умолчанию в Laravel и движок Tailwind JIT, убедитесь, что ключ content файла tailwind.config.js вашего приложения ссылается на представления пагинации Laravel, чтобы их классы Tailwind не были удалены:

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

Основное использование

Пагинация результатов запроса Query Builder

Есть несколько способов создания пагинации элементов. Самый простой - использовать метод paginate в построителе запросов или Eloquent-запросе. Метод paginate автоматически устанавливает параметры "limit" и "offset" запроса в зависимости от текущей страницы, просматриваемой пользователем. По умолчанию текущая страница определяется значением аргумента строки запроса page в HTTP-запросе. Это значение автоматически обнаруживается Laravel и также автоматически вставляется в ссылки, созданные пагинатором.

В этом примере единственным аргументом, переданным методу paginate, является количество элементов, которые вы хотели бы отображать "на странице". В этом случае давайте укажем, что мы хотим отображать 15 элементов на странице:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
 
class UserController extends Controller
{
/**
* Показать всех пользователей приложения.
*/
public function index(): View
{
return view('user.index', [
'users' => DB::table('users')->paginate(15)
]);
}
}

Простая пагинация

Метод paginate подсчитывает общее количество записей, соответствующих запросу, до извлечения записей из базы данных. Это делается для того, чтобы пагинатор знал, сколько всего страниц с записями. Однако, если вы не планируете показывать общее количество страниц в пользовательском интерфейсе вашего приложения, запрос на подсчет записей необязателен.

Поэтому, если вам нужно отображать простые ссылки "Next" и "Previous" в пользовательском интерфейсе вашего приложения, вы можете использовать метод simplePaginate для выполнения одного эффективного запроса:

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

Пагинация результатов Eloquent

Вы также можете создавать пагинацию для запросов Eloquent. В этом примере мы будем создавать пагинацию для модели App\Models\User и указываем, что мы собираемся отображать 15 записей на странице. Как видите, синтаксис почти идентичен с пагинацией результатов построителя запросов:

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

Конечно же, вы можете вызывать метод paginate после установки других ограничений запроса, таких как клаузы where:

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

Вы также можете использовать метод simplePaginate при создании пагинации для моделей Eloquent:

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

Аналогично вы можете использовать метод cursorPaginate для создания пагинации на основе курсора для моделей Eloquent:

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

Несколько экземпляров Paginator на страницу

Иногда вам может потребоваться отобразить два отдельных пагинатора на одном экране, созданном вашим приложением. Однако, если оба экземпляра пагинатора используют параметр строки запроса page для хранения текущей страницы, они будут конфликтовать. Чтобы разрешить этот конфликт, вы можете передать имя параметра строки запроса, которое вы хотите использовать для хранения текущей страницы пагинатора, через третий аргумент, предоставленный методам paginate, simplePaginate и cursorPaginate:

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

Cursor Pagination

В то время как paginate и simplePaginate создают запросы с использованием SQL-клаузу "offset", курсорная пагинация работает путем создания клауз "where", которые сравнивают значения упорядоченных столбцов, содержащихся в запросе, обеспечивая наилучшую производительность базы данных среди всех методов пагинации Laravel. Этот метод пагинации особенно подходит для больших наборов данных и интерфейсов пользователя с "бесконечным" прокручиванием.

В отличие от пагинации на основе смещения, которая включает номер страницы в строку запроса URL, созданную пагинатором, пагинация на основе курсора размещает строку "курсора" в строке запроса. Курсор - это закодированная строка, содержащая местоположение, с которого следующий запрос для пагинации должен начать пагинацию, и направление, в котором следует пагинировать:

http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0

Вы можете создать экземпляр пагинатора на основе курсора с помощью метода cursorPaginate, предоставляемого построителем запросов. Этот метод возвращает экземпляр Illuminate\Pagination\CursorPaginator:

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

После получения экземпляра пагинатора на основе курсора вы можете отобразить результаты пагинации, как обычно это делается при использовании методов paginate и simplePaginate. Для получения дополнительной информации о методах экземпляра пагинатора на основе курсора обратитесь к документации по методам экземпляра пагинатора на основе курсора.

Внимание Ваш запрос должен содержать клаузу "order by", чтобы воспользоваться пагинацией на основе курсора.

Cursor vs. Offset Pagination

Для пояснения различий между пагинацией на основе смещения и пагинацией на основе курсора давайте рассмотрим несколько примеров SQL-запросов. Оба следующих запроса отобразят "вторую страницу" результатов для таблицы users, упорядоченной по 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;

Запрос пагинации на основе курсора предлагает следующие преимущества по сравнению с пагинацией на основе смещения:

  • Для больших наборов данных пагинация на основе курсора обеспечивает лучшую производительность, если столбцы "order by" проиндексированы. Это связано с тем, что клауза "offset" сканирует все ранее сопоставленные данные.
  • Для наборов данных с частыми записями пагинация на основе смещения может пропускать записи или показывать дубликаты, если результаты были недавно добавлены или удалены из страницы, которую в данный момент просматривает пользователь.

Однако у пагинации на основе курсора есть следующие ограничения:

  • Как и simplePaginate, пагинация на основе курсора может использоваться только для отображения ссылок "Next" и "Previous" и не поддерживает создание ссылок с номерами страниц.
  • Требуется, чтобы сортировка была основана хотя бы на одном уникальном столбце или комбинации столбцов, которые являются уникальными. Столбцы со значениями null не поддерживаются.
  • Выражения запросов в клаузах "order by" поддерживаются только в том случае, если они именованы (алиасы) и добавлены также в клаузу "select".
  • Выражения запросов с параметрами не поддерживаются.

Создание пагинатора вручную

Иногда вам может потребоваться создать экземпляр пагинатора вручную, передав ему массив элементов, которые у вас уже есть в памяти. Вы можете сделать это, создав экземпляр Illuminate\Pagination\Paginator, Illuminate\Pagination\LengthAwarePaginator или Illuminate\Pagination\CursorPaginator, в зависимости от ваших потребностей.

Классам Paginator и CursorPaginator не требуется знать об общем количестве элементов в наборе результатов; однако из-за этого у этих классов нет методов для получения индекса последней страницы. LengthAwarePaginator принимает почти те же аргументы, что и Paginator; однако ему требуется количество общее количество элементов в наборе результатов.

Другими словами, Paginator соответствует методу simplePaginate в построителе запросов, CursorPaginator соответствует методу cursorPaginate, а LengthAwarePaginator соответствует методу paginate.

Внимание При создании экземпляра пагинатора вручную вы должны вручную "разделить" массив результатов, который вы передаете пагинатору. Если вы не уверены, как это сделать, ознакомьтесь с функцией PHP array_slice.

Настройка URL-адресов пагинации

По умолчанию ссылки, создаваемые пагинатором, будут соответствовать URI текущего запроса. Однако метод withPath пагинатора позволяет настраивать URI, используемый пагинатором при создании ссылок. Например, если вы хотите, чтобы пагинатор создавал ссылки вида http://example.com/admin/users?page=N, вы должны передать /admin/users методу withPath:

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

Добавление значений строки запроса

Вы можете добавлять к строке запроса ссылок пагинации с помощью метода appends. Например, чтобы добавить sort=votes к каждой ссылке пагинации, вы должны сделать следующий вызов appends:

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

Вы можете использовать метод withQueryString, если хотите добавить все значения текущей строки запроса к ссылкам пагинации:

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

Добавление фрагментов хеша

Если вам нужно добавить "фрагмент хэша" к URL-адресам, создаваемым пагинатором, вы можете использовать метод fragment. Например, чтобы добавить #users в конец каждой ссылки пагинации, вы должны вызвать метод fragment следующим образом:

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

Отображение результатов пагинации

При вызове метода paginate вы получите экземпляр Illuminate\Pagination\LengthAwarePaginator, а вызов метода simplePaginate вернет экземпляр Illuminate\Pagination\Paginator. И, наконец, вызов метода cursorPaginate вернет экземпляр Illuminate\Pagination\CursorPaginator.

Эти объекты предоставляют несколько методов, описывающих набор результатов. Помимо этих вспомогательных методов, экземпляры пагинаторов являются итераторами и могут быть перебраны как массив. Так что, получив результаты, вы можете отобразить их и отобразить ссылки на страницы с использованием Blade:

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

Метод links будет отображать ссылки на остальные страницы в наборе результатов. Каждая из этих ссылок уже будет содержать правильную переменную строки запроса page. Помните, что HTML, созданный методом links, совместим с фреймворком Tailwind CSS.

Настройка окна пагинации

Когда пагинатор отображает ссылки пагинации, отображается также текущий номер страницы, а также ссылки на три страницы перед и после текущей страницы. С помощью метода onEachSide вы можете контролировать, сколько дополнительных ссылок отображается с каждой стороны текущей страницы внутри среднего, скользящего окна ссылок, созданных пагинатором:

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

Преобразование результатов в JSON

Классы пагинаторов Laravel реализуют интерфейс Illuminate\Contracts\Support\Jsonable и предоставляют метод toJson, поэтому очень легко преобразить ваши результаты пагинации в JSON. Вы также можете преобразить экземпляр пагинатора в JSON, возвращая его из маршрута или действия контроллера:

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

JSON от пагинатора будет включать метаинформацию, такую ​​как total, current_page, last_page и другие. Результаты записей доступны через ключ data в массиве JSON. Вот пример JSON, созданного возвращением экземпляра пагинатора из маршрута:

{
"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":[
{
// Запись...
},
{
// Запись...
}
]
}

Настройка вида пагинации

По умолчанию представления, рендерящие ссылки пагинации, совместимы с фреймворком Tailwind CSS. Однако, если вы не используете Tailwind, вы свободны определить свои собственные представления для отображения этих ссылок. При вызове метода links на экземпляре пагинатора вы можете передать имя представления в качестве первого аргумента методу:

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

Однако самый простой способ настроить представления пагинации - это экспортировать их в ваш каталог resources/views/vendor с помощью команды vendor:publish:

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

Эта команда поместит представления в каталог resources/views/vendor/pagination вашего приложения. Файл tailwind.blade.php внутри этого каталога соответствует представлению пагинации по умолчанию. Вы можете изменить этот файл, чтобы изменить HTML-разметку пагинации.

Если вы хотите назначить другой файл в качестве представления пагинации по умолчанию, вы можете вызвать методы defaultView и defaultSimpleView пагинатора внутри метода boot вашего класса App\Providers\AppServiceProvider:

<?php
 
namespace App\Providers;
 
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Загрузка любых служб приложения.
*/
public function boot(): void
{
Paginator::defaultView('view-name');
 
Paginator::defaultSimpleView('view-name');
}
}

Использование Bootstrap

Laravel включает представления пагинации, созданные с использованием Bootstrap CSS. Чтобы использовать эти представления вместо представлений Tailwind по умолчанию, вы можете вызвать методы useBootstrapFour или useBootstrapFive пагинатора внутри метода boot вашего класса App\Providers\AppServiceProvider:

use Illuminate\Pagination\Paginator;
 
/**
* Загрузка любых служб приложения.
*/
public function boot(): void
{
Paginator::useBootstrapFive();
Paginator::useBootstrapFour();
}

Методы экземпляра Paginator / LengthAwarePaginator

Каждый экземпляр пагинатора предоставляет дополнительную информацию о пагинации с помощью следующих методов:

Метод Описание
$paginator->count() Получить количество элементов для текущей страницы.
$paginator->currentPage() Получить номер текущей страницы.
$paginator->firstItem() Получить порядковый номер первого элемента в результатах.
$paginator->getOptions() Получить параметры пагинатора.
$paginator->getUrlRange($start, $end) Создать диапазон URL-адресов пагинации.
$paginator->hasPages() Определить, достаточно ли элементов для разделения на несколько страниц.
$paginator->hasMorePages() Определить, есть ли еще элементы в хранилище данных.
$paginator->items() Получить элементы для текущей страницы.
$paginator->lastItem() Получить порядковый номер последнего элемента в результатах.
$paginator->lastPage() Получить номер последней доступной страницы. (Недоступно при использовании simplePaginate).
$paginator->nextPageUrl() Получить URL для следующей страницы.
$paginator->onFirstPage() Определить, находится ли пагинатор на первой странице.
$paginator->perPage() Количество элементов, отображаемых на странице.
$paginator->previousPageUrl() Получить URL для предыдущей страницы.
$paginator->total() Определить общее количество соответствующих элементов в хранилище данных. (Недоступно при использовании simplePaginate).
$paginator->url($page) Получить URL для заданного номера страницы.
$paginator->getPageName() Получить переменную строки запроса, используемую для хранения номера страницы.
$paginator->setPageName($name) Установить переменную строки запроса, используемую для хранения номера страницы.
$paginator->through($callback) Преобразовать каждый элемент, используя обратный вызов.

Методы экземпляра Cursor Paginator

Каждый экземпляр пагинатора на основе курсора предоставляет дополнительную информацию о пагинации с помощью следующих методов:

Метод Описание
$paginator->count() Получить количество элементов для текущей страницы.
$paginator->cursor() Получить текущий экземпляр курсора.
$paginator->getOptions() Получить параметры пагинатора.
$paginator->hasPages() Определить, достаточно ли элементов для разделения на несколько страниц.
$paginator->hasMorePages() Определить, есть ли еще элементы в хранилище данных.
$paginator->getCursorName() Получить переменную строки запроса, используемую для хранения курсора.
$paginator->items() Получить элементы для текущей страницы.
$paginator->nextCursor() Получить экземпляр курсора для следующего набора элементов.
$paginator->nextPageUrl() Получить URL для следующей страницы.
$paginator->onFirstPage() Определить, находится ли пагинатор на первой странице.
$paginator->onLastPage() Определить, находится ли пагинатор на последней странице.
$paginator->perPage() Количество элементов, отображаемых на странице.
$paginator->previousCursor() Получить экземпляр курсора для предыдущего набора элементов.
$paginator->previousPageUrl() Получить URL для предыдущей страницы.
$paginator->setCursorName() Установить переменную строки запроса, используемую для хранения курсора.
$paginator->url($cursor) Получить URL для заданного экземпляра курсора.