Категории

Как правильно сделать пагинацию в категории блога на PHP без ошибок

2025-12-12 10:15:54 | Статья из категории: Создание сайтов

Как правильно сделать пагинацию в категории блога на PHP без ошибок

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

Разберём, **где зарыта собака**, и как сделать пагинацию в категориях **точно как на главной**, но без боли.

❌ Основные косяки (и как их избежать)

1. Смешивание позиционных и именованных параметров в PDO

Ты пишешь:

$stmt = $pdo->prepare("SELECT * FROM articles WHERE category_id = ? LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
$stmt->execute([$category_id]); // ← ОШИБКА!

→ PDO ругается: mixed named and positional parameters.

Почему? Ты используешь и ? (позиционный), и :limit (именованный) в одном запросе. Так нельзя.

Решение: замени ? на :category_id и используй только bindValue().

2. Переменная $per_page не определена

Если объявить $articles_per_page внутри if или после запроса — PHP не увидит её. Тогда bindValue(':limit', $per_page, ...) передаст NULL.

→ В SQL получится: LIMIT NULL OFFSET 0 → MariaDB падает с синтаксической ошибкой.

Решение: определи лимиты **в самом начале скрипта**, до любого SQL.

3. Сломанная HTML-разметка

Если вставить </body> до контента — браузер просто не отобразит ничего. Особенно если у тебя в header.php уже открыт <body>.

Проверь: в categories.php не должно быть </body> до </div></body></html> в самом конце!

✅ Рабочий код: пагинация в categories.php (как на главной)

Цель: как на index.php — статьи по 10, коды по 6, один параметр page, одна пагинация (считаем по статьям).

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

session_start();
require 'include/db.php';

// === Параметры ===
$category_id = intval($_GET['id'] ?? 0);
$type = $_GET['type'] ?? 'all';
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;

// === ВАЖНО: лимиты ДО запросов! ===
$articles_per_page = 10;
$codes_per_page = 6;
$offset = ($page - 1) * $articles_per_page;

// === Проверка категории ===
if ($category_id <= 0) {
    http_response_code(400);
    include '404.php';
    exit;
}

$stmt = $pdo->prepare("SELECT name FROM categories WHERE id = :id");
$stmt->bindValue(':id', $category_id, PDO::PARAM_INT);
$stmt->execute();
$category = $stmt->fetch();

if (!$category) {
    http_response_code(404);
    include '404.php';
    exit;
}

// === Подсчёт страниц (только по статьям, как на главной) ===
$count_stmt = $pdo->prepare("SELECT COUNT(*) FROM articles WHERE category_id = :category_id");
$count_stmt->bindValue(':category_id', $category_id, PDO::PARAM_INT);
$count_stmt->execute();
$total_articles = $count_stmt->fetchColumn();
$total_pages = max(1, ceil($total_articles / $articles_per_page));

include 'includes/header.php';
?>

<title><?= htmlspecialchars($category['name']) ?> | blog.iotprof.ru</title>
<meta name="description" content="Статьи и код-посты по категории: <?= htmlspecialchars($category['name']) ?>">
<link rel="stylesheet" href="/css/style.css">

</head>
<body>

<div class="container">
    <?php include 'includes/category_block.php'; ?>

    <div class="category-header">
        <h1><?= htmlspecialchars($category['name']) ?></h1>
    </div>

    <div class="category-filters">
        <a href="?id=<?= $category_id ?>&type=all" class="filter-btn <?= ($type === 'all') ? 'active' : '' ?>">Все</a>
        <a href="?id=<?= $category_id ?>&type=article" class="filter-btn <?= ($type === 'article') ? 'active' : '' ?>">Статьи</a>
        <a href="?id=<?= $category_id ?>&type=code" class="filter-btn <?= ($type === 'code') ? 'active' : '' ?>">Код</a>
    </div>

    <?php if ($type === 'all' || $type === 'article'): ?>
        <section class="posts-section">
            <h2>Статьи</h2>
            <div class="posts-grid">
                <?php
                $stmt = $pdo->prepare("
                    SELECT a.id, a.title, a.preview_text, a.image, a.created_at, c.name as category
                    FROM articles a
                    LEFT JOIN categories c ON a.category_id = c.id
                    WHERE a.category_id = :category_id
                    ORDER BY a.created_at DESC
                    LIMIT :limit OFFSET :offset
                ");
                $stmt->bindValue(':category_id', $category_id, PDO::PARAM_INT);
                $stmt->bindValue(':limit', $articles_per_page, PDO::PARAM_INT);
                $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
                $stmt->execute();

                if ($stmt->rowCount() > 0):
                    while ($row = $stmt->fetch()):
                ?>
                    <article class="post-card">
                        <?php if ($row['image']): ?>
                            <img loading="lazy" src="/uploads/<?= htmlspecialchars($row['image']) ?>" alt="<?= htmlspecialchars($row['title']) ?>" class="post-image">
                        <?php endif; ?>
                        <div class="post-content">
                            <span class="date_cat_article"><?= $row['category'] ?: 'Без категории' ?> | <?= date('Y-m-d', strtotime($row['created_at'])) ?></span>
                            <h3><a href="article.php?id=<?= $row['id'] ?>"><?= htmlspecialchars($row['title']) ?></a></h3>
                            <p><?= nl2br(htmlspecialchars($row['preview_text'])) ?></p>
                        </div>
                    </article>
                <?php endwhile; else: ?>
                    <p class="no-items">Нет статей</p>
                <?php endif; ?>
            </div>
        </section>
    <?php endif; ?>

    <?php if ($type === 'all' || $type === 'code'): ?>
        <section class="posts-section">
            <h2>Примеры кода</h2>
            <div class="posts-grid">
                <?php
                $stmt = $pdo->prepare("
                    SELECT cp.id, cp.title, cp.intro AS preview_text, cp.image, cp.created_at, c.name as category
                    FROM code_posts cp
                    LEFT JOIN categories c ON cp.category_id = c.id
                    WHERE cp.category_id = :category_id
                    ORDER BY cp.created_at DESC
                    LIMIT :limit OFFSET :offset
                ");
                $stmt->bindValue(':category_id', $category_id, PDO::PARAM_INT);
                $stmt->bindValue(':limit', $codes_per_page, PDO::PARAM_INT);
                $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
                $stmt->execute();

                if ($stmt->rowCount() > 0):
                    while ($row = $stmt->fetch()):
                ?>
                    <article class="post-card code-card">
                        <?php if ($row['image']): ?>
                            <img loading="lazy" src="/uploads/<?= htmlspecialchars($row['image']) ?>" alt="<?= htmlspecialchars($row['title']) ?>" class="post-image">
                        <?php endif; ?>
                        <div class="post-content">
                            <span class="category-tag"><?= $row['category'] ?: 'Без категории' ?> | <?= date('Y-m-d', strtotime($row['created_at'])) ?></span>
                            <h3><a href="code_post.php?id=<?= $row['id'] ?>"><?= htmlspecialchars($row['title']) ?></a></h3>
                            <p><?= nl2br(htmlspecialchars($row['preview_text'])) ?></p>
                        </div>
                    </article>
                <?php endwhile; else: ?>
                    <p class="no-items">Нет примеров кода</p>
                <?php endif; ?>
            </div>
        </section>
    <?php endif; ?>

    <!-- Пагинация (считаем по статьям, как на главной) -->
    <div class="pagination">
        <?php if ($page > 1): ?>
            <a href="?id=<?= $category_id ?>&type=<?= $type ?>&page=<?= $page - 1 ?>" class="btn">← Назад</a>
        <?php endif; ?>
        <span>Страница <?= $page ?> из <?= $total_pages ?></span>
        <?php if ($page < $total_pages): ?>
            <a href="?id=<?= $category_id ?>&type=<?= $type ?>&page=<?= $page + 1 ?>" class="btn">Вперёд →</a>
        <?php endif; ?>
    </div>

    <a href="/" class="back-btn">← На главную</a>
</div>

<?php
include 'includes/callpack.php';
include 'includes/footer.php';
?>

💡 Советы на будущее

Заключение

Теперь категории грузятся быстро, без сотни картинок, и без падений. Если у тебя самописный движок — такие мелочи решают всё.

Удачи в кодинге! И помни: даже «говно-палочный» DIY-проект начинается с одной рабочей строки PHP 😉

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Важно: Блог-эксперимент

Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


кто я | книга | контакты

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA