Использование поля type=0 для базовых разделов контента

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

Реализация раздела с типом 0 в PHP CMS

Как организовать базовый раздел контента с типом 0, используя единую архитектуру?

В большинстве самодельных CMS на PHP в таблице sections числовое поле type определяет предназначение записи. Значение 0 обычно резервируется для стандартного раздела с текстовым содержимым, которое вводит редактор через WYSIWYG редактор. Такие разделы не являются категориями, ссылками или модулями, а представляют собой самостоятельные страницы со своим уникальным URL и содержимым.

Основное эффективное решение состоит из трех частей: структура базы данных, модель (или класс работы с БД) и контроллер вывода. Ниже приведен минимальный пример реализации на PHP с использованием PDO.

1. Структура таблицы sections


CREATE TABLE sections (
    id INT AUTO_INCREMENT PRIMARY KEY,
    parent_id INT DEFAULT 0,
    title VARCHAR(255) NOT NULL,
    alias VARCHAR(255) UNIQUE NOT NULL,
    type TINYINT DEFAULT 0,
    content TEXT,
    published TINYINT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
  

2. Получение раздела по alias


// db.php - настройка подключения
$db = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8', 'user', 'pass');

// Получаем раздел по alias
function getSectionByAlias($db, $alias) {
    $stmt = $db->prepare('SELECT * FROM sections WHERE alias = :alias AND type = 0 AND published = 1 LIMIT 1');
    $stmt->execute(['alias' => $alias]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
}
  

3. Вывод содержимого


// page.php
if (isset($_GET['alias'])) {
    $section = getSectionByAlias($db, $_GET['alias']);
    if ($section) {
        echo '

' . htmlspecialchars($section['title']) . '

'; echo '
' . $section['content'] . '
'; } else { echo 'Раздел не найден или недоступен.'; } }

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

  • Забыть установить type = 0 при создании раздела, тогда он не будет найден фильтром.
  • Не экранировать вывод через htmlspecialchars при выводе заголовка – уязвимость XSS.
  • Не обрабатывать случай, когда alias не передан в URL – вызов ошибки.

Как сделать, чтобы разделы типа 0 отображались только авторизованным пользователям?

Для ограничения доступа к разделам с типом 0 можно добавить проверку прав перед выводом содержимого. Ниже пример с использованием простой сессии.


session_start();

function canViewSection($db, $userId, $sectionId) {
    // Проверка, что пользователь имеет роль администратора или специальное разрешение
    $stmt = $db->prepare('SELECT role FROM users WHERE id = :id');
    $stmt->execute(['id' => $userId]);
    $user = $stmt->fetch();
    return $user && $user['role'] === 'admin';
}

if ($section && canViewSection($db, $_SESSION['user_id'], $section['id'])) {
    echo $section['content'];
} else {
    echo 'Доступ запрещен.';
}
  

Проблемы и решения:

  • Если сессия не стартована – ошибка обращения к $_SESSION. Всегда добавлять session_start() в начале скрипта.
  • Лучше использовать более безопасный механизм аутентификации, например, JWT.

Как создать раздел типа 0 через административную панель с обработкой формы?

Администратор должен иметь возможность ввести заголовок, алиас и HTML-контент. В запросе INSERT значение type=0 задается явно.


// admin_create.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $title = $_POST['title'] ?? '';
    $alias = $_POST['alias'] ?? '';
    $content = $_POST['content'] ?? '';

    if (empty($title) || empty($alias)) {
        echo 'Заголовок и алиас обязательны.';
    } else {
        $stmt = $db->prepare('INSERT INTO sections (title, alias, type, content) VALUES (:title, :alias, 0, :content)');
        $stmt->execute(['title' => $title, 'alias' => $alias, 'content' => $content]);
        echo 'Раздел создан.';
    }
}
  

Как избежать дублирования алиасов и защититься от SQL-инъекций?

Используйте подготовленные запросы и уникальный индекс на поле alias. При попытке вставить дубликат выбросится исключение, которое нужно отловить.


try {
    $stmt->execute(...);
} catch (PDOException $e) {
    if ($e->getCode() == 23000) {
        echo 'Алиас уже существует.';
    } else {
        echo 'Ошибка базы данных.';
    }
}
  

Как в шаблоне отобразить все разделы типа 0 в виде меню?

Для построения меню достаточно получить список разделов с фильтром по типу и опубликованным статусом.


$stmt = $db->query('SELECT title, alias FROM sections WHERE type = 0 AND published = 1 ORDER BY title');
$sections = $stmt->fetchAll(PDO::FETCH_ASSOC);

if ($sections) {
    echo '';
}
  

Возможные проблемы:

  • При большом количестве разделов без пагинации меню станет громоздким. Рекомендуется ограничить вывод первыми 20 записями или добавить поиск.
  • URL должен быть построен правильно, чтобы не было битых ссылок.

Как хранить дополнительные поля для разделов типа 0 (например, изображение)?

Для расширения функционала без изменения основной таблицы можно использовать связанную таблицу section_meta с ключом-значением.


CREATE TABLE section_meta (
    id INT AUTO_INCREMENT PRIMARY KEY,
    section_id INT NOT NULL,
    meta_key VARCHAR(100) NOT NULL,
    meta_value TEXT,
    FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE
);

// Получение изображения для раздела
$stmt = $db->prepare('SELECT meta_value FROM section_meta WHERE section_id = :id AND meta_key = "image"');
$stmt->execute(['id' => $section['id']]);
$image = $stmt->fetchColumn();
if ($image) {
    echo '';
}
  

Цель такого подхода:

Гибкое добавление произвольных полей без изменения схемы основной таблицы. Подходит для большинства CMS.

Расширенные примеры работы с разделом типа 0

Пример 1. Кэширование вывода раздела типа 0 для повышения производительности

Код:

Пример

function getCachedSection($db, $alias, $cacheTime = 3600) {
    $cacheKey = 'section_' . md5($alias);
    $data = apcu_fetch($cacheKey);
    if ($data === false) {
        $stmt = $db->prepare('SELECT * FROM sections WHERE alias = :alias AND type = 0 AND published = 1 LIMIT 1');
        $stmt->execute(['alias' => $alias]);
        $data = $stmt->fetch(PDO::FETCH_ASSOC);
        apcu_store($cacheKey, $data, $cacheTime);
    }
    return $data;
}

$section = getCachedSection($db, $_GET['alias']);
if ($section) {
    echo '

' . htmlspecialchars($section['title']) . '

'; echo $section['content']; }

Результат:

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

Пример 2. Полнотекстовый поиск по содержимому разделов типа 0

Код:

Пример

$search = $_GET['q'] ?? '';
if (strlen($search) >= 3) {
    $stmt = $db->prepare('SELECT title, alias FROM sections WHERE type = 0 AND MATCH(content) AGAINST(:q IN BOOLEAN MODE) LIMIT 20');
    $stmt->execute(['q' => $search . '*']);
    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($results as $r) {
        echo '

' . htmlspecialchars($r['title']) . '

'; } }

Результат:

Вводит поисковый запрос, и пользователь видит ссылки на разделы типа 0, в содержимом которых встречается это слово. Требуется предварительно создать полнотекстовый индекс на поле content.

Пример 3. Импорт разделов типа 0 из CSV файла

Код:

Пример

$file = fopen('sections.csv', 'r');
fgetcsv($file); // пропускаем заголовок
$db->beginTransaction();
try {
    while (($row = fgetcsv($file)) !== false) {
        $title = $row[0];
        $alias = $row[1];
        $content = $row[2];
        $stmt = $db->prepare('INSERT INTO sections (title, alias, type, content) VALUES (:title, :alias, 0, :content)');
        $stmt->execute(['title' => $title, 'alias' => $alias, 'content' => $content]);
    }
    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    echo 'Ошибка импорта: ' . $e->getMessage();
}
fclose($file);

Результат:

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

Пример 4. Генерация карты сайта только для разделов типа 0

Код (sitemap.php):

Пример

header('Content-Type: application/xml; charset=utf-8');
$stmt = $db->query('SELECT alias, updated_at FROM sections WHERE type = 0 AND published = 1');
$sections = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
foreach ($sections as $s) {
    echo '<url>';
    echo '<loc>https://example.com/page.php?alias=' . urlencode($s['alias']) . '</loc>';
    echo '<lastmod>' . date('Y-m-d', strtotime($s['updated_at'])) . '</lastmod>';
    echo '</url>';
}
echo '</urlset>';

Результат:

XML файл содержит ссылки на все разделы типа 0, что помогает поисковым системам индексировать только значимые страницы контента, исключая категории и внешние ссылки.
- Section php id 2 (раздел php с id=2)
- Section php code (код раздела php)
- View php section (просмотр раздела php)
- Section php url (url раздела php)
- Edit php section (редактирование раздела php)
- Sections php id (разделы с id в php)
- Indices php section (индексы разделов php)
- Details php section (детали раздела php)
- Sections php lang id (разделы с языком и id php)
- List php section id (список разделов по id php)
- Section 3 php id (раздел php с id=3)
- Php section 1 (раздел php с id=1)
- Listing php section (список разделов php)
- Section php type 0 (тип раздела 0 php)
- Section php lang (раздел с языком php)
- Catalog php section id (каталог разделов по id php)
- View section php id section (просмотр раздела по id php)
- Section php id 0 (раздел php с id=0)
- Index php section id (маршрут index.php с id раздела php)
- Section php id 4 (раздел php с id=4)
- Sections php (разделы в php)
- Catalog section php (каталог разделов php)

Тип раздела 0 PHP - comments

En
Section php type 0 (php)