Создание страницы категории на PHP: от простого к сложному

Раздел: Веб-программирование на PHP -> Структура сайта

Реализация страницы категории на PHP

Основной эффективный вариант: централизованный роутинг с ЧПУ

Цель: организовать страницу категории так, чтобы URL были читаемыми (например, /category/novosti), а логика обрабатывалась единым контроллером. Этот подход считается стандартом в современных PHP-приложениях, так как он безопасен, гибок и легко поддерживается.

Как это работает:

  1. Все запросы перенаправляются на index.php через .htaccess.
  2. В index.php анализируется путь (request_uri) и извлекается сегмент категории.
  3. Выполняется запрос к базе данных для получения записей этой категории.
  4. Результат передается в шаблон для отображения.

Пример минимальной структуры:


/.htaccess
/index.php
/app/controllers/CategoryController.php
/app/models/Category.php
/app/views/category.php
    

Файл .htaccess


RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
    

Файл index.php


<?php
$url = $_GET['url'] ?? '';
$segments = explode('/', trim($url, '/'));

if (isset($segments[0]) && $segments[0] === 'category' && isset($segments[1])) {
    $categorySlug = $segments[1];
    require 'app/controllers/CategoryController.php';
    $controller = new CategoryController();
    $controller->show($categorySlug);
} else {
    // 404 или другая обработка
    header('HTTP/1.0 404 Not Found');
    echo 'Страница не найдена';
}
    

Файл app/controllers/CategoryController.php


<?php
class CategoryController {
    public function show($slug) {
        $categoryModel = new Category();
        $category = $categoryModel->getBySlug($slug);
        if (!$category) {
            header('HTTP/1.0 404 Not Found');
            echo 'Категория не найдена';
            exit;
        }
        $posts = $categoryModel->getPosts($category['id']);
        include 'app/views/category.php';
    }
}
    

Файл app/models/Category.php


<?php
class Category {
    public function getBySlug($slug) {
        // Пример с PDO
        $db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
        $stmt = $db->prepare('SELECT * FROM categories WHERE slug = ?');
        $stmt->execute([$slug]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    public function getPosts($categoryId) {
        $db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
        $stmt = $db->prepare('SELECT * FROM posts WHERE category_id = ?');
        $stmt->execute([$categoryId]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
    

Файл app/views/category.php


<h2><?= htmlspecialchars($category['name']) ?></h2>
<?php foreach ($posts as $post): ?>
    <article>
        <h3><?= htmlspecialchars($post['title']) ?></h3>
        <p><?= nl2br(htmlspecialchars($post['content'])) ?></p>
    </article>
<?php endforeach; ?>
    

Типичные ошибки и их решения

  • Ошибка 500 из-за синтаксиса в .htaccess (например, лишних пробелов). Решение: проверить файл через онлайн-валидатор mod_rewrite.
  • Запросы выполняются медленно из-за отсутствия индексов в БД. Решение: добавить индекс на поле slug и category_id.
  • SQL-инъекция при прямом встраивании переменных. Решение: всегда использовать подготовленные запросы (PDO или mysqli).
  • Проблемы с кодировкой (кракозябры). Решение: установить charset=utf8 в DSN и заголовок Content-Type: text/html; charset=utf-8.

Как отобразить записи определенной категории с помощью GET-параметра?

Самый простой способ: передавать идентификатор категории через ?cat=ID. Подходит для небольших проектов или прототипов, но неудобен для пользователей и SEO.


<?php
$catId = (int)($_GET['cat'] ?? 0);
if ($catId > 0) {
    $db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
    $stmt = $db->prepare('SELECT * FROM posts WHERE category_id = ?');
    $stmt->execute([$catId]);
    $posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($posts as $post) {
        echo '<h3>' . htmlspecialchars($post['title']) . '</h3>';
    }
}
?>
    

Проблемы

  • URL неинформативен: ?cat=1 не говорит о сущности категории.
  • Легко поддается атакам (SQL-инъекции при неправильной обработке).
  • Нет возможности сделать человеко-понятные ссылки.

Решение: все же использовать ЧПУ и валидировать входные данные.

Как создать ЧПУ для страницы категории с помощью mod_rewrite?

Этот вариант улучшает первый, добавляя правило перезаписи URL. В итоге у вас есть ЧПУ, но логика остается в одном файле.


RewriteEngine On
RewriteRule ^category/([a-z0-9-]+)$ index.php?cat_slug=$1 [L,QSA]
    

В index.php обрабатываем $_GET['cat_slug'] аналогично примеру выше.

Ошибка: если правило написано неверно, может возникнуть цикл перенаправления или некорректное срабатывание. Решение: использовать флаг [L] и проверять URL в браузере.

Как организовать страницу категории в MVC-архитектуре?

Полноценный MVC подразумевает разделение на модели, контроллеры и представления. В этом варианте каждый компонент отвечает за свою часть. Приведенный в основном решении код и есть пример простого MVC. Главное преимущество: легко добавлять новые типы страниц и поддерживать код.


// Дополнительный пример с наследованием контроллера
class CategoryController extends BaseController {
    public function show($slug) {
        $this->view->render('category', ['category' => $this->model->getBySlug($slug)]);
    }
}
    

Ошибка: слишком много классов для маленького проекта. Решение: не злоупотреблять MVC, если приложение состоит из 2-3 страниц.

Расширенные примеры и сценарии

Пример 1: Страница категории с пагинацией

Пример

<?php
// В контроллере
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 10;
$offset = ($page - 1) * $perPage;
$posts = $categoryModel->getPostsPaginated($categoryId, $perPage, $offset);
$totalPosts = $categoryModel->countPosts($categoryId);
$totalPages = ceil($totalPosts / $perPage);

// В представлении
for ($i = 1; $i <= $totalPages; $i++): ?>
    <a href="?page=<?= $i ?>"><?= $i ?></a>
<?php endfor; ?>
Вывод: ссылки на страницы (1 2 3 ...) внизу списка записей.

Пример 2: Обработка пустой категории

Пример

<?php
if (empty($posts)) {
    echo '<p class="fw-bold">В этой категории пока нет записей.</p>';
} else {
    foreach ($posts as $post) {
        echo '<div>' . htmlspecialchars($post['title']) . '</div>';
    }
}
Если записей нет, выводится сообщение, иначе список.

Пример 3: Сортировка записей (по дате, по названию)

Пример

<?php
$order = $_GET['order'] ?? 'date_desc';
switch ($order) {
    case 'date_asc':
        $sqlOrder = 'created_at ASC';
        break;
    case 'title_asc':
        $sqlOrder = 'title ASC';
        break;
    default:
        $sqlOrder = 'created_at DESC';
}
$stmt = $db->prepare("SELECT * FROM posts WHERE category_id = ? ORDER BY $sqlOrder");
$stmt->execute([$catId]);
Пользователь может переключать сортировку через параметр ?order=title_asc.

Пример 4: Использование подготовленных запросов для предотвращения SQL-инъекций

Пример

<?php
$db = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$slug = $_GET['slug'] ?? '';
$stmt = $db->prepare('SELECT * FROM categories WHERE slug = :slug');
$stmt->bindParam(':slug', $slug, PDO::PARAM_STR);
$stmt->execute();
$category = $stmt->fetch(PDO::FETCH_ASSOC);
В случае передачи вредоносного значения в slug запрос останется безопасным.

Пример 5: Обработка ошибки 404 для несуществующей категории

Пример

<?php
$categoryModel = new Category();
$category = $categoryModel->getBySlug($slug);
if (!$category) {
    http_response_code(404);
    include 'errors/404.php';
    exit;
}
Браузер получит статус 404, что важно для SEO.

Пример 6: Создание URL для категории в шаблоне

Пример

<?php
function categoryUrl($slug) {
    return '/category/' . urlencode($slug);
}
// Использование
?>
<a href="<?= categoryUrl($category['slug']) ?>"><?= htmlspecialchars($category['name']) ?></a>
Генерируется правильный ЧПУ-URL.

Пример 7: Использование кэширования для страниц категорий

Пример

<?php
$cacheKey = 'category_' . $slug . '_page_' . $page;
$cached = apcu_fetch($cacheKey);
if ($cached === false) {
    // Генерация содержимого
    ob_start();
    // ... вывод представления
    $content = ob_get_clean();
    apcu_store($cacheKey, $content, 3600); // кэш на 1 час
    echo $content;
} else {
    echo $cached;
}
Повышает производительность при большом количестве посещений.

Страница категории на PHP - comments

En
Cat page php page (php)