Управление категориями через 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; // ноутбуки-и-аксессуары
ноутбуки-и-аксессуары