Управление категориями через index.php?id=cats

Раздел: Веб-разработка -> Управление контентом

Вывод и управление категориями контента через параметр id=cats

Наиболее эффективное решение: PDO с подготовленными запросами и кэшированием

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

Решение основано на PDO (PHP Data Objects) - универсальном интерфейсе доступа к базам данных. Подготовленные запросы предотвращают SQL-инъекции, а кэширование (например, через Redis) снижает нагрузку на базу.


<?php
// Пример с PDO и кэшированием через файл
$cacheFile = __DIR__ . '/cache/categories.cache';
$cacheTime = 3600; // 1 час

if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
    $categories = unserialize(file_get_contents($cacheFile));
} else {
    $dsn = 'mysql:host=localhost;dbname=example;charset=utf8';
    $pdo = new PDO($dsn, 'user', 'pass');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $stmt = $pdo->query('SELECT id, name, parent_id FROM categories ORDER BY sort_order');
    $categories = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // Кэширование
    file_put_contents($cacheFile, serialize($categories));
}

// Вывод (учитываем parent_id для иерархии)
foreach ($categories as $cat) {
    echo "<a href='?id=cats&cat_id={$cat['id']}'>{$cat['name']}</a><br>";
}
?>
    

Tags php tag list (список тегов в php)

Возможные проблемы и ошибки

  • Некорректный путь к файлу кэша - необходимо проверить права на запись.
  • Отсутствие драйвера PDO для MySQL - требуется установка расширения pdo_mysql.
  • Слишком длительное время кэширования - при частых изменениях данных нужно сократить $cacheTime или использовать более гибкое кэширование (Redis).

Как вывести категории с использованием устаревшего расширения mysql_*?

Этот вариант приведён только для понимания истории; его не следует применять в новых проектах из-за отсутствия поддержки и безопасности.


$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('example', $link);
$result = mysql_query('SELECT id, name FROM categories');
while ($row = mysql_fetch_assoc($result)) {
    echo $row['name'] . '<br>';
}
mysql_close($link);
    

Content pages php page (страница контента на php)

Проблемы:

  • Расширение удалено в PHP 7.0.
  • Отсутствует защита от SQL-инъекций.
  • Нет поддержки подготовленных запросов.

Как организовать вывод категорий через mysqli с подготовленными запросами?

mysqli поддерживает процедурный и объектный стиль. Подготовленные запросы обеспечивают безопасность.


$mysqli = new mysqli('localhost', 'user', 'pass', 'example');
$stmt = $mysqli->prepare('SELECT id, name, parent_id FROM categories WHERE parent_id = ?');
$parentId = isset($_GET['parent']) ? (int)$_GET['parent'] : 0;
$stmt->bind_param('i', $parentId);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    echo "<a href='?id=cats&cat_id={$row['id']}'>{$row['name']}</a><br>";
}
$stmt->close();
    

Index php content id 9 (вывод контента по id в php)

Типичные ошибки:

  • Неверный порядок аргументов в bind_param - типы и переменные должны соответствовать.
  • Игнорирование обработки ошибок - следует проверять $mysqli->error.

Как реализовать иерархию категорий с помощью рекурсии (adjacency list)?

Если категории имеют родительскую связь (parent_id), можно построить дерево рекурсивно. Подходит для небольших объёмов.


function buildTree(array $elements, $parentId = 0) {
    $branch = array();
    foreach ($elements as $element) {
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }
    return $branch;
}

// Получение всех категорий (например, через PDO)
$stmt = $pdo->query('SELECT id, name, parent_id FROM categories ORDER BY sort_order');
$allCategories = $stmt->fetchAll(PDO::FETCH_ASSOC);
$tree = buildTree($allCategories);
print_r($tree);
    

Index php id cats (категории контента)

Ограничения: при большом количестве записей рекурсия может быть медленной и потреблять много памяти. Альтернатива - nested sets или closure table.

Как использовать ORM (например, Eloquent) для работы с категориями?

ORM упрощает код, но добавляет накладные расходы. Подходит для проектов с фреймворками (Laravel).


// Модель Category
class Category extends Model {
    protected $table = 'categories';
}

// Контроллер
$categories = Category::where('parent_id', null)->with('children')->get();
foreach ($categories as $cat) {
    echo "<li>{$cat->name}";
    if ($cat->children->count()) {
        echo "<ul>";
        foreach ($cat->children as $child) {
            echo "<li>{$child->name}</li>";
        }
        echo "</ul>";
    }
    echo "</li>";
}
    

View product php id (страница товара на php)

Проблемы: необходимость установки фреймворка и зависимостей; при неоптимальных запросах (N+1) возможны проблемы с производительностью.

Как добавить AJAX-подгрузку категорий без перезагрузки страницы?

Для улучшения UX можно загружать категории через fetch/XMLHttpRequest. Сервер возвращает JSON.


// PHP: index.php?id=cats&ajax=1
if (isset($_GET['ajax'])) {
    header('Content-Type: application/json');
    $stmt = $pdo->query('SELECT id, name FROM categories');
    echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
    exit;
}
// JavaScript (фрагмент)
fetch('index.php?id=cats&ajax=1')
    .then(res => res.json())
    .then(data => {
        data.forEach(cat => {
            document.getElementById('cat-list').innerHTML +=
                `<a href="?id=cats&cat_id=${cat.id}">${cat.name}</a><br>`;
        });
    });
    

Ошибки: некорректная обработка HTTP-заголовков, отсутствие CSRF-защиты при POST-запросах.

Расширенные примеры реализации категорий

Построение дерева категорий с помощью nested sets (модель MPTT)

Метод позволяет быстро получать поддерево без рекурсии. Структура таблицы: id, name, lft, rgt, level.

Пример

<?php
function getSubtree($pdo, $id) {
    // Получаем lft и rgt корневой категории
    $stmt = $pdo->prepare('SELECT lft, rgt FROM categories WHERE id = ?');
    $stmt->execute([$id]);
    $root = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$root) return [];

    // Все узлы с lft между root.lft и root.rgt
    $stmt = $pdo->prepare('SELECT id, name, level FROM categories WHERE lft BETWEEN ? AND ? ORDER BY lft');
    $stmt->execute([$root['lft'], $root['rgt']]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

// Вывод дерева
$tree = getSubtree($pdo, 1);
foreach ($tree as $node) {
    $prefix = str_repeat('--', $node['level']);
    echo "{$prefix}{$node['name']}<br>";
}
?>
Результат для категорий: "Электроника", "--Ноутбуки", "--Телефоны", "---Смартфоны"
--Ноутбуки
--Телефоны
---Смартфоны

Кэширование категорий в Redis

Использование Redis для кэширования ускоряет доступ и снимает нагрузку с БД.

Пример

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = 'categories_all';

if ($redis->exists($cacheKey)) {
    $categories = unserialize($redis->get($cacheKey));
} else {
    $stmt = $pdo->query('SELECT id, name, parent_id FROM categories ORDER BY sort_order');
    $categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $redis->setex($cacheKey, 3600, serialize($categories));
}

foreach ($categories as $cat) {
    echo htmlspecialchars($cat['name'], ENT_QUOTES, 'UTF-8') . '<br>';
}
?>
Вывод списка категорий из кэша (при его наличии) или из базы.

Пагинация (постраничный вывод) категорий

При большом количестве категорий удобно разбить вывод на страницы.

Пример

<?php
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$perPage = 10;
$offset = ($page - 1) * $perPage;

$totalStmt = $pdo->query('SELECT COUNT(*) FROM categories');
$total = $totalStmt->fetchColumn();

$stmt = $pdo->prepare('SELECT id, name FROM categories LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($categories as $cat) {
    echo "<a href='?id=cats&cat_id={$cat['id']}'>{$cat['name']}</a><br>";
}

// Пагинация
$totalPages = ceil($total / $perPage);
for ($i = 1; $i <= $totalPages; $i++) {
    echo "<a href='?id=cats&page={$i}'>{$i}</a> ";
}
?>
Страница 1: Категория 1, Категория 2 ... (10 записей). Ссылки на другие страницы.

Closure table для вложенных категорий

Closure table - отдельная таблица paths, хранящая все предки-потомки. Позволяет гибко получать поддеревья.

Пример

// Таблица categories: id, name
// Таблица categories_closure: ancestor, descendant, depth

function getAllChildren($pdo, $categoryId) {
    $stmt = $pdo->prepare('
        SELECT c.id, c.name 
        FROM categories c 
        JOIN categories_closure cc ON c.id = cc.descendant
        WHERE cc.ancestor = ? AND cc.depth > 0
    ');
    $stmt->execute([$categoryId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

$children = getAllChildren($pdo, 2);
foreach ($children as $child) {
    echo $child['name'] . '<br>';
}
Вывод всех потомков категории с id=2.

Генерация ЧПУ (slug) для категорий

Преобразование названия в читаемый URL.

Пример

function generateSlug($text) {
    $text = mb_strtolower($text, 'UTF-8');
    $text = preg_replace('/[^\w\s-]/u', '', $text);
    $text = preg_replace('/\s+/', '-', $text);
    return trim($text, '-');
}

// Пример
$name = 'Ноутбуки и аксессуары';
$slug = generateSlug($name);
echo $slug; // ноутбуки-и-аксессуары
ноутбуки-и-аксессуары

Категории контента - comments

En
Index php id cats (php)