← Назад к вопросам

Сталкивался ли с реализацией пагинации?

1.6 Junior🔥 242 комментариев
#API и веб-протоколы#Базы данных и SQL

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация пагинации в PHP Backend-разработке

Да, я многократно сталкивался с реализацией пагинации в различных проектах, поскольку это фундаментальный механизм для работы с большими объёмами данных. Пагинация — это не просто "разбиение на страницы", а комплексная задача, затрагивающая производительность, UX и архитектуру приложения.

Основные подходы к реализации

1. Пагинация на уровне базы данных (SQL)

Наиболее эффективный подход, когда данные извлекаются порциями непосредственно из СУБД. Пример с MySQL и LIMIT:

SELECT id, title, created_at FROM articles 
WHERE status = 'published'
ORDER BY created_at DESC
LIMIT 20 OFFSET 40; -- 3-я страница при 20 записях на странице
class Paginator {
    public function paginateQuery(string $table, int $page, int $perPage): array {
        $offset = ($page - 1) * $perPage;
        
        $query = "SELECT * FROM {$table} LIMIT {$perPage} OFFSET {$offset}";
        // Выполнение запроса через PDO или ORM
        
        return [
            'data' => $results,
            'current_page' => $page,
            'per_page' => $perPage,
            'total' => $this->getTotalCount($table)
        ];
    }
    
    private function getTotalCount(string $table): int {
        // COUNT(*) запрос для получения общего количества
    }
}

2. Пагинация через ORM (Eloquent в Laravel)

Современные фреймворки предоставляют встроенные средства:

// Laravel Eloquent
$users = User::where('active', true)
    ->orderBy('name')
    ->paginate(15); // Автоматически определяет текущую страницу из запроса

// Возвращает объект LengthAwarePaginator с метаданными
return response()->json([
    'data' => $users->items(),
    'meta' => [
        'current_page' => $users->currentPage(),
        'per_page' => $users->perPage(),
        'total' => $users->total(),
        'last_page' => $users->lastPage()
    ],
    'links' => $users->linkCollection()->toArray()
]);

3. Пагинация с курсорами для бесконечной прокрутки

Альтернатива классической пагинации, особенно для бесконечных лент:

class CursorPaginator {
    public function paginate(string $cursor = null, int $limit = 20): array {
        $query = Post::orderBy('created_at', 'desc')
                    ->orderBy('id', 'desc');
        
        if ($cursor) {
            list($date, $id) = explode('_', base64_decode($cursor));
            $query->where('created_at', '<', $date)
                  ->orWhere(function($q) use ($date, $id) {
                      $q->where('created_at', $date)
                        ->where('id', '<', $id);
                  });
        }
        
        $posts = $query->limit($limit + 1)->get();
        
        $hasNextPage = $posts->count() > $limit;
        if ($hasNextPage) {
            $posts->pop(); // Удаляем лишний элемент
        }
        
        $nextCursor = null;
        if ($hasNextPage && $lastPost = $posts->last()) {
            $nextCursor = base64_encode("{$lastPost->created_at}_{$lastPost->id}");
        }
        
        return [
            'data' => $posts,
            'next_cursor' => $nextCursor,
            'has_next_page' => $hasNextPage
        ];
    }
}

Ключевые аспекты реализации

Производительность:

  • Всегда используйте COUNT(*) с условиями WHERE, если они есть
  • Для очень больших таблиц рассматривайте приблизительный подсчёт или кеширование
  • Индексация полей в ORDER BY и WHERE критически важна

Безопасность:

  • Валидация и санитизация параметров пагинации
  • Ограничение максимального значения per_page
  • Подготовленные запросы для предотвращения SQL-инъекций

Архитектурные решения:

  • DTO для пагинационных данных — стандартизация ответа API
  • Репозиторий с поддержкой пагинации — отделение логики доступа к данным
  • Пагинация в GraphQL — реализация через connections и edges по спецификации Relay

Проблемы и их решения

  1. Смещение данных при добавлении новых записей — при частых изменениях данных пользователь может получить дубликаты или пропуски. Решение: курсорная пагинация или стабильная сортировка.

  2. Производительность COUNT(*) на больших таблицах — используйте приблизительные подсчёты (например, EXPLAIN SELECT в MySQL) или материализованные представления.

  3. Сложные фильтры и поиск — кешируйте результаты подсчёта или используйте поисковые движки (Elasticsearch) со встроенной пагинацией.

Современные практики

В REST API стандартизирован ответ через envelope-структуру с разделением данных и метаинформации. В GraphQL популярен подход Relay Cursor Connections Specification. Для мобильных приложений часто предпочитают бесконечную прокрутку с курсорной пагинацией вместо классической постраничной навигации.

Пагинация остается активной областью развития: новые подходы включают пагинацию на основе временных диапазонов, гибридные решения и интеграцию с виртуализацией списков на фронтенде.