Пагинация — штука простая, пока не начнёшь её внедрять в реальный проект. Я полдня потратил, чтобы понять, почему после правки categories.php сайт стал показывать пустую страницу или сыпать ошибками вроде:
SQLSTATE[HY093]: Invalid parameter numberUndefined variable $per_page... near 'NULL OFFSET 0'Разберём, **где зарыта собака**, и как сделать пагинацию в категориях **точно как на главной**, но без боли.
Ты пишешь:
$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().
Если объявить $articles_per_page внутри if или после запроса — PHP не увидит её. Тогда bindValue(':limit', $per_page, ...) передаст NULL.
→ В SQL получится: LIMIT NULL OFFSET 0 → MariaDB падает с синтаксической ошибкой.
Решение: определи лимиты **в самом начале скрипта**, до любого SQL.
Если вставить </body> до контента — браузер просто не отобразит ничего. Особенно если у тебя в header.php уже открыт <body>.
Проверь: в categories.php не должно быть </body> до </div></body></html> в самом конце!
Цель: как на 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';
?>
error_reporting(E_ALL) при отладке.? и :named в PDO — выбирай одно.</body> в середине!Теперь категории грузятся быстро, без сотни картинок, и без падений. Если у тебя самописный движок — такие мелочи решают всё.
Удачи в кодинге! И помни: даже «говно-палочный» DIY-проект начинается с одной рабочей строки PHP 😉
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!