Связывание страниц и разделов в PHP-приложении

Раздел: Веб-разработка на PHP -> Работа с контентом: страницы и разделы

Основные подходы к работе со страницами по ID раздела

Наиболее эффективное решение

Для получения списка страниц, принадлежащих определённому разделу, оптимально использовать подготовленные запросы PDO. Это обеспечивает защиту от SQL-инъекций и хорошую производительность при правильной индексации.

// Пример базового запроса
$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8mb4', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM pages WHERE section_id = :id');
$stmt->execute(['id' => $sectionId]);
$pages = $stmt->fetchAll();

Pages pages php id section (страницы php с id раздела)

Пояснение шагов:

  1. Создаётся объект PDO с корректным DSN и кодировкой.
  2. Метод prepare готовит запрос с именованным плейсхолдером :id.
  3. При вызове execute передаётся массив с реальным значением идентификатора раздела.
  4. Метод fetchAll возвращает все подходящие строки в виде массива.

Цель использования: быстрый и безопасный отбор страниц конкретного раздела для вывода на странице категории или в навигации.

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

При необходимости вывести не только страницы, но и имя их раздела применяется оператор JOIN. Это уменьшает количество запросов к базе данных.

$sql = 'SELECT p.*, s.title AS section_title 
        FROM pages p 
        JOIN sections s ON p.section_id = s.id 
        WHERE s.id = :id';
$stmt = $pdo->prepare($sql);
$stmt->execute(['id' => $sectionId]);
$data = $stmt->fetchAll();

В результате каждая запись содержит поле section_title, доступное для вывода.

Когда удобно фильтровать страницы на стороне PHP без WHERE?

Если требуется отобразить все страницы и только потом выделить относящиеся к определённому разделу (например, для построения дерева), можно загрузить все записи и отфильтровать массив средствами PHP.

$allPages = $pdo->query('SELECT * FROM pages')->fetchAll();
$filtered = array_filter($allPages, function($page) use ($sectionId) {
    return $page['section_id'] == $sectionId;
});

Недостаток: при большом количестве страниц возрастает нагрузка на память и время выполнения. Метод оправдан лишь для небольших объёмов данных или когда нужна полная выборка для дополнительной обработки.

Как закэшировать результат для повышения производительности?

При частом обращении к одним и тем же страницам раздела целесообразно кэшировать результат запроса. В простейшем случае можно использовать файловый кэш.

$cacheKey = 'pages_section_' . $sectionId;
$cacheFile = __DIR__ . '/cache/' . md5($cacheKey) . '.cache';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
    $pages = unserialize(file_get_contents($cacheFile));
} else {
    $stmt = $pdo->prepare('SELECT * FROM pages WHERE section_id = ?');
    $stmt->execute([$sectionId]);
    $pages = $stmt->fetchAll();
    file_put_contents($cacheFile, serialize($pages));
}

Время жизни кэша (3600 секунд) корректируется под конкретные требования актуальности данных.

Типичные проблемы и способы их решения

  • SQL-инъекция из-за конкатенации. Никогда не вставляйте $sectionId напрямую в строку запроса. Решение: использование плейсхолдеров PDO или MySQLi prepared statements.
  • Проблема N+1 запросов. При выводе страниц и названий разделов в цикле для каждой страницы выполняется отдельный запрос. Решение: применение JOIN или жадная загрузка (eager loading) в ORM.
  • Отсутствие индекса на колонке section_id. Это приводит к полному сканированию таблицы pages. Решение: создать индекс: CREATE INDEX idx_section_id ON pages(section_id);
  • Несовпадение типов данных. Если section_id имеет тип INTEGER, а в запрос передаётся строка, MySQL может выполнить неявное преобразование, что снижает производительность и может дать неожиданные результаты. Решение: явно приводить тип к целому числу с помощью intval() или указывать PDO::PARAM_INT.

Цели и случаи использования

  • Построение списка статей по категории (блог, новости).
  • Формирование динамического меню на основе разделов и дочерних страниц.
  • Создание фильтров для интернет-магазина, где товары привязаны к разделам каталога.
  • Административный интерфейс для управления содержимым конкретного раздела.

Пример 1: Базовое получение страниц с PDO и обработка ошибок

Рассмотрим полный скрипт с проверкой соединения и обработкой исключений.

Пример
try {
    $pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', '', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    $sectionId = 5;
    $stmt = $pdo->prepare('SELECT id, title, content FROM pages WHERE section_id = :id');
    $stmt->execute(['id' => $sectionId]);
    $pages = $stmt->fetchAll();
    
    if (empty($pages)) {
        echo "

Страницы для раздела $sectionId не найдены.

"; } else { echo "
    "; foreach ($pages as $page) { echo "
  • {$page['title']}
  • "; } echo "
"; } } catch (PDOException $e) { echo "Ошибка базы данных: " . $e->getMessage(); }
Вывод (если есть страницы с section_id=5):
  • О компании
  • Контакты

Пояснение: при ошибке подключения или запроса скрипт перехватывает исключение и выводит сообщение, не прерывая выполнение остальной части приложения.

Пример 2: Вывод страниц с именем раздела через JOIN

Допустим, требуется показать таблицу со страницами и названиями их разделов.

Пример
$sectionId = 2;
$sql = 'SELECT p.id, p.title AS page_title, s.name AS section_name
        FROM pages p
        JOIN sections s ON p.section_id = s.id
        WHERE s.id = :sid';
$stmt = $pdo->prepare($sql);
$stmt->execute(['sid' => $sectionId]);
$result = $stmt->fetchAll();

echo "";
foreach ($result as $row) {
    echo "";
}
echo "
IDСтраницаРаздел
{$row['id']}{$row['page_title']}{$row['section_name']}
";
Таблица с результатом:
ID  | Страница       | Раздел
1   | Главная        | Основные
2   | О нас          | Основные
3   | Услуги         | Сервисы

Пояснение: JOIN объединяет две таблицы по ключу section_id, что позволяет извлечь данные обеих сущностей за один запрос.

Пример 3: Пагинация с учётом раздела

Когда страниц много, удобно разбивать вывод на страницы. Ниже пример с лимитом 10 записей на страницу.

Пример
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$sectionId = 3;

$countStmt = $pdo->prepare('SELECT COUNT(*) FROM pages WHERE section_id = :sid');
$countStmt->execute(['sid' => $sectionId]);
$total = (int)$countStmt->fetchColumn();
$totalPages = ceil($total / $limit);

$dataStmt = $pdo->prepare('SELECT * FROM pages WHERE section_id = :sid LIMIT :lim OFFSET :off');
$dataStmt->bindValue(':sid', $sectionId, PDO::PARAM_INT);
$dataStmt->bindValue(':lim', $limit, PDO::PARAM_INT);
$dataStmt->bindValue(':off', $offset, PDO::PARAM_INT);
$dataStmt->execute();
$pages = $dataStmt->fetchAll();

// Вывод страниц и ссылок пагинации
echo "";
Страницы раздела 3 (показаны первые 10):
[Страница 1] [Страница 2] ... [Страница 5]
Ссылки: 1 2 3 4 5

Пояснение: используется два запроса: один для подсчёта общего числа записей, второй для выборки с ограничением. Параметры LIMIT и OFFSET передаются как целые числа через bindValue.

Пример 4: Использование кэширования с Memcached

Для высоконагруженных проектов применяется Memcached. Ключ формируется на основе ID раздела.

Пример
$memcache = new Memcached();
$memcache->addServer('localhost', 11211);

$cacheKey = 'section_pages_' . $sectionId;
$pages = $memcache->get($cacheKey);

if (!$pages) {
    $stmt = $pdo->prepare('SELECT * FROM pages WHERE section_id = ?');
    $stmt->execute([$sectionId]);
    $pages = $stmt->fetchAll();
    $memcache->set($cacheKey, $pages, 3600); // кэш на 1 час
}

foreach ($pages as $page) {
    echo $page['title'] . '
'; }
Вывод названий страниц раздела.

Пояснение: при первом обращении данные загружаются из БД и сохраняются в Memcached. Последующие запросы получают данные из кэша, что значительно ускоряет работу.

Пример 5: Работа с ORM (Eloquent)

В Laravel связь «страница принадлежит разделу» определяется через метод belongsTo. Выборка страниц по ID раздела выполняется простым условием.

Пример
// В модели Page (app/Models/Page.php)
class Page extends Model {
    public function section() {
        return $this->belongsTo(Section::class);
    }
}

// В контроллере
$pages = Page::where('section_id', $sectionId)->get();

foreach ($pages as $page) {
    echo $page->title . ' (' . $page->section->name . ')';
}
Вывод списка страниц с названиями разделов.

Пояснение: ORM скрывает детали SQL-запросов и автоматически подгружает связанные данные при обращении к свойству section. Однако при массовой выборке следует использовать with('section') для избежания проблемы N+1.

Страницы PHP с ID раздела - comments

En
Pages pages php id section (php)