Управление контентом на PHP: ключевые детали и примеры

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

Реализация управления контентом на PHP

Основной подход: база данных MySQL с PDO

Как организовать хранение и вывод контента с защитой от SQL-инъекций?

Создадим таблицу pages:


CREATE TABLE pages (
  id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(255) NOT NULL,
  content TEXT,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  

PHP-код для получения страницы:


$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8mb4', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT title, content FROM pages WHERE id = ?');
$stmt->execute([$_GET['id'] ?? 1]);
$page = $stmt->fetch();
if (!$page) { exit('Страница не найдена'); }
$title = htmlspecialchars($page['title']);
$content = nl2br(htmlspecialchars($page['content']));
echo "<h1>$title</h1><div>$content</div>";
  

Шаг 1: Подключение к БД с указанием кодировки utf8mb4. Шаг 2: Подготовка запроса с плейсхолдером для предотвращения инъекций. Шаг 3: Выполнение с передачей параметра. Шаг 4: Экранирование вывода через htmlspecialchars и nl2br для сохранения переносов строк.

Типичные ошибки:
  • Использование устаревшего mysql_*. Решение: применять PDO или mysqli.
  • Пропуск подготовки запроса при вставке переменных. Решение: всегда использовать подготовленные выражения.
  • Отсутствие обработки исключений PDO. Решение: установка PDO::ATTR_ERRMODE в PDO::ERRMODE_EXCEPTION.
  • Забыть про экранирование вывода – угроза XSS. Решение: htmlspecialchars с флагами ENT_QUOTES и кодировкой UTF-8.

Вариант: хранение контента в JSON-файлах

Как сделать простую CMS без базы данных?

Файл data.json:


[
  {"id":1,"title":"Главная","content":"<p>Добро пожаловать</p>"},
  {"id":2,"title":"Контакты","content":"<p>Email: info@example.com</p>"}
]
  

PHP-код для чтения:


$json = file_get_contents('data.json');
$pages = json_decode($json, true);
$id = $_GET['id'] ?? 1;
$filtered = array_filter($pages, fn($p) => $p['id'] == $id);
$page = reset($filtered);
if ($page) {
    $title = htmlspecialchars($page['title']);
    $content = $page['content']; // уже HTML, но всё равно экранируем
    echo "<h1>$title</h1><div>$content</div>";
} else {
    http_response_code(404);
    echo 'Страница не найдена';
}
  

Шаги: чтение файла, декодирование, фильтрация, вывод с экранированием.

Проблемы:
  • Конкурентный доступ: при одновременной записи нескольких пользователей файл может быть повреждён. Решение: использовать файловую блокировку flock() или перейти на SQLite.
  • Отсутствие структуры: при большом объёме данных поиск неэффективен. Решение: перейти на БД.
  • Безопасность: если содержимое содержит PHP-код, он не выполнится, но JSON-файл может быть изменён злоумышленником. Решение: хранить файлы вне document root.

Вариант: использование Markdown-файлов

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

Файл about.md:


# О компании
Текст с **полужирным** и *курсивом*.
Ссылка: (https://example.com)[Example]
  

PHP с библиотекой Parsedown:


require 'Parsedown.php';
$parsedown = new Parsedown();
$md = file_get_contents('pages/about.md');
if ($md === false) { http_response_code(404); exit; }
$html = $parsedown->text($md);
echo $html;
  

Шаги: загрузка файла, парсинг Markdown в HTML, вывод. Для кэширования результата можно сохранять скомпилированный HTML в отдельный файл.

Проблемы:
  • Обработка ошибок: файл может отсутствовать или быть недоступным для чтения.
  • Производительность: при каждом запросе парсинг Markdown. Решение: кэшировать результат в файл или в память (APCu).
  • Безопасность: пользовательский ввод в Markdown может содержать XSS, поэтому после парсинга нужно пропускать через HTML Purifier или экранировать теги.

Вариант: шаблонизатор Twig для вывода контента

Как отделить логику от представления и упростить разработку?

Установка через Composer: composer require twig/twig

Шаблон page.html.twig:


{% extends "base.html.twig" %}
{% block content %}
  <h1>{{ title }}</h1>
  <div>{{ content|raw }}</div>
{% endblock %}
  

PHP-код:


require_once 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, ['cache' => 'cache/twig', 'autoescape' => true]);
// получение данных из БД или файла
$pageData = ['title' => 'Главная', 'content' => '<p>Привет</p>'];
echo $twig->render('page.html.twig', $pageData);
  

Шаги: настройка автозагрузки, загрузчика шаблонов, конфигурация окружения, рендеринг с передачей переменных.

Типичные ошибки:
  • Забыть установить кэш, что замедляет работу. Решение: указать директорию для кэша.
  • Использование raw без необходимости может привести к XSS. Решение: если контент уже безопасен (например, сгенерирован через htmlspecialchars), то raw допустимо. Иначе применять фильтр escape.
  • Неправильные пути к шаблонам. Решение: проверять права доступа и пути.

Расширенные примеры работы с контентом на PHP

Пример 1: Полный CRUD для страниц через PDO с админ-интерфейсом

Создание страницы (create.php):

Пример

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8mb4', 'user', 'pass', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
    $stmt = $pdo->prepare('INSERT INTO pages (title, content) VALUES (:title, :content)');
    $stmt->execute(['title' => $_POST['title'], 'content' => $_POST['content']]);
    header('Location: list.php');
    exit;
}
?>
<form method="post">
    <input type="text" name="title" required>
    <textarea name="content"></textarea>
    <button type="submit">Сохранить</button>
</form>
  

Результат (list.php) выводит список страниц:

Пример

<?php
$pdo = new PDO('...');
$stmt = $pdo->query('SELECT id, title FROM pages');
while ($row = $stmt->fetch()) {
    echo "<a href='edit.php?id={$row['id']}'>{$row['title']}</a><br>";
}
  
Главная
О нас
Контакты
  

Редактирование (edit.php):

Пример

<?php
$id = $_GET['id'];
$pdo = new PDO('...');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $stmt = $pdo->prepare('UPDATE pages SET title=:title, content=:content WHERE id=:id');
    $stmt->execute(['id' => $id, 'title' => $_POST['title'], 'content' => $_POST['content']]);
    header('Location: list.php');
    exit;
}
$stmt = $pdo->prepare('SELECT * FROM pages WHERE id=:id');
$stmt->execute(['id' => $id]);
$page = $stmt->fetch();
?>
<form method="post">
    <input type="text" name="title" value="<?= $page['title'] ?>">
    <textarea name="content"><?= $page['content'] ?></textarea>
    <button>Обновить</button>
</form>
  

Удаление (delete.php):

Пример

<?php
$id = $_GET['id'];
$pdo = new PDO('...');
$stmt = $pdo->prepare('DELETE FROM pages WHERE id=:id');
$stmt->execute(['id' => $id]);
header('Location: list.php');
  

Все операции следует защищать авторизацией (сессии, проверка прав). В коде не показана обработка CSRF, что является ошибкой. Решение: добавлять токены к формам.

Пример 2: Кэширование скомпилированного Markdown в файле

Чтобы не парсить Markdown каждый раз, можно кэшировать HTML:

Пример

$cacheFile = 'cache/' . md5($pageId) . '.html';
$ttl = 3600; // 1 час
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) {
    echo file_get_contents($cacheFile);
    exit;
}
// парсинг Markdown
$md = file_get_contents('pages/' . $pageId . '.md');
$html = $parsedown->text($md);
file_put_contents($cacheFile, $html);
echo $html;
  

Результат: при повторном запросе в течение часа файл читается из кэша, что значительно ускоряет работу. Ошибкой является отсутствие проверки на успешность записи в кэш. Также надо обеспечить уникальность имени кэш-файла для разных страниц.

(Время генерации: 0.001 сек вместо 0.05 сек)
  

Пример 3: Загрузка и вставка изображений в контент через обработку форм

Форма загрузки:

Пример

<form method="post" enctype="multipart/form-data">
    <input type="file" name="image" accept="image/*">
    <button>Загрузить</button>
</form>
  

PHP-обработчик:

Пример

$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (in_array($_FILES['image']['type'], $allowedTypes)) {
    $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
    $filename = uniqid() . '.' . $ext;
    move_uploaded_file($_FILES['image']['tmp_name'], 'uploads/' . $filename);
    echo '<img src="uploads/' . $filename . '">';
} else {
    echo 'Недопустимый тип файла';
}
  

Результат: изображение сохраняется в папку uploads и выводится на странице. Ошибки: проверка только MIME-типа не гарантирует безопасность (можно подделать). Решение: проверять расширение, содержимое через getimagesize() и ограничивать размер файла.

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

Детали раздела PHP - comments

En
Details php section (php)