Реализация страницы статьи: от файлов до базы данных

Раздел: Разработка сайтов -> Контент

Вывод статьи на PHP: пошаговое руководство

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

Основное решение: извлечение статьи из базы данных MySQL с помощью PDO и отображение через шаблонизатор.

Рассмотрим пример, где статья хранится в таблице articles с полями id, title, body, created_at. Пользователь переходит по адресу /article.php?id=5.

<?php
// article.php - базовый пример с PDO
$id = (int)$_GET['id'] ?? 0;
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM articles WHERE id = ?');
$stmt->execute([$id]);
$article = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$article) {
    http_response_code(404);
    echo 'Статья не найдена';
    exit;
}
?>
<!DOCTYPE html>
<html>
<head><title><?= htmlspecialchars($article['title']) ?></title></head>
<body>
<h2><?= htmlspecialchars($article['title']) ?></h2>
<div><?= nl2br(htmlspecialchars($article['body'])) ?></div>
</body>
</html>

Article php page (статья на php)

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

  1. Получение идентификатора статьи из URL с приведением к целому числу.
  2. Подключение к базе данных через PDO с указанием кодировки utf8.
  3. Подготовленный запрос предотвращает SQL-инъекции.
  4. Если статья не найдена, возвращается код 404 и сообщение.
  5. Вывод данных с использованием htmlspecialchars для защиты от XSS.
  6. Функция nl2br преобразует переносы строк в HTML-теги.

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

  • Забывают проверять существование $_GET['id'], что вызывает warning.
  • Используют устаревшие функции mysql_*.
  • Не экранируют вывод, что ведёт к XSS-уязвимости.
  • Не задают кодировку соединения, получают кракозябры.

Как вывести статью из файла (txt, markdown) без базы данных?

Вариант: чтение контента из текстового файла.

Подходит для небольших сайтов или статичного контента. Файлы статей хранятся в папке /articles/ с именем article_id.txt.

<?php
$id = basename($_GET['slug'] ?? ''); // slug вместо id, безопасное имя
$file = __DIR__ . '/articles/' . $id . '.txt';
if (!file_exists($file)) {
    http_response_code(404);
    echo 'Статья не найдена';
    exit;
}
$content = file_get_contents($file);
$title = strtok($content, "\n"); // первая строка как заголовок
$body = substr($content, strlen($title)+1);
?>
<h2><?= htmlspecialchars($title) ?></h2>
<pre><?= htmlspecialchars($body) ?></pre>

Index php view article (индекс php просмотр статьи)

Проблемы: отсутствие структуры метаданных, сложность поддержки, невозможность сортировки.

Как организовать вывод статьи из JSON-файла?

Вариант: хранение статей в одном файле articles.json.

<?php
$articles = json_decode(file_get_contents('articles.json'), true);
$slug = $_GET['slug'] ?? '';
$article = null;
foreach ($articles as $a) {
    if ($a['slug'] === $slug) {
        $article = $a;
        break;
    }
}
if (!$article) { /* 404 */ }
?>

Подходит для прототипов. Недостаток: при большом количестве статей замедляется, нет индексации.

Как вывести статью из внешнего API (например, WordPress REST API)?

Вариант: загрузка статьи через cURL.

<?php
$id = (int)$_GET['id'];
$ch = curl_init("https://example.com/wp-json/wp/v2/posts/$id");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) { /* ошибка */ }
$post = json_decode($response, true);
echo htmlspecialchars($post['title']['rendered']);
echo $post['content']['rendered']; // уже HTML, но нужна фильтрация
?>

Проблемы: зависимость от внешнего сервера, лаги, кэширование обязательно.

Как сделать маршрутизацию (роутинг) для страниц статей?

Вариант: использование .htaccess + front controller.

# .htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^article/(.+)$ article.php?slug=$1 [L,QSA]
<?php
// article.php
$slug = $_GET['slug'] ?? '';
// извлечение статьи по slug
?>

Как защитить страницу статьи от прямого доступа без ID?

Вариант: проверка наличия параметра и его валидация.

if (!isset($_GET['id']) || !ctype_digit($_GET['id'])) {
    http_response_code(400);
    exit('Некорректный запрос');
}

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

Вариант: простое кэширование в файл.

$cacheFile = __DIR__ . '/cache/article_' . $id . '.html';
if (file_exists($cacheFile) && (filemtime($cacheFile) > time() - 3600)) {
    readfile($cacheFile);
    exit;
}
ob_start();
// ... вывод статьи ...
$html = ob_get_clean();
file_put_contents($cacheFile, $html);
echo $html;

Расширенные примеры реализации страницы статьи

Пример 1: Статья с использованием Markdown (Parsedown)

Подключаем библиотеку Parsedown через Composer или вручную. Храним тело статьи в формате Markdown в БД или файле.

Пример
<?php
require 'Parsedown.php';
$Parsedown = new Parsedown();
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'user', 'pass');
$stmt = $pdo->prepare('SELECT title, body_md FROM articles WHERE id = ?');
$stmt->execute([$id]);
$article = $stmt->fetch();
$htmlBody = $Parsedown->text($article['body_md']); // преобразует Markdown в HTML
?>
<h2><?= htmlspecialchars($article['title']) ?></h2>
<div class='article-body'>
    <?= $htmlBody ?> 
    <!-- НЕ экранируем htmlspecialchars, так как это безопасный HTML от Parsedown -->
</div>

Результат: на экране отображается отформатированный текст с заголовками, списками, ссылками. Проблемы: недостаточная изоляция, возможны XSS, если контент содержит нежелательный HTML. Рекомендуется использовать библиотеку HTMLPurifier после рендеринга.

Пример 2: Статья с пагинацией (разбивка на страницы)

Если статья длинная, её можно разбить на части. В базе хранится поле body_parts (JSON массив).

Пример
<?php
$parts = json_decode($article['body_parts'], true);
$totalParts = count($parts);
$currentPart = (int)($_GET['part'] ?? 1);
$currentPart = max(1, min($currentPart, $totalParts));
$currentBody = $parts[$currentPart - 1];
?>
<div><?= nl2br(htmlspecialchars($currentBody)) ?></div>
<nav>
<?php if ($currentPart > 1): ?>
    <a href='?id=<?= $id ?>&part=<?= $currentPart-1 ?>'>Предыдущая часть</a>
<?php endif; ?>
<?php if ($currentPart < $totalParts): ?>
    <a href='?id=<?= $id ?>&part=<?= $currentPart+1 ?>'>Следующая часть</a>
<?php endif; ?>
</nav>

Результат: пользователь листает части статьи.

Пример 3: Статья с SEO-URL (человеко-понятные адреса)

Используем slug из базы. В таблице articles есть поле slug уникальное. ЧПУ: /article/kak-ustanovit-php.

Пример
// .htaccess
RewriteRule ^article/([a-z0-9-]+)$ article.php?slug=$1 [L,QSA]

// article.php
$slug = $_GET['slug'] ?? '';
$stmt = $pdo->prepare('SELECT * FROM articles WHERE slug = ?');
$stmt->execute([$slug]);
$article = $stmt->fetch();
// 404 если нет
?>
<!-- мета-теги -->
<link rel='canonical' href='/article/<?= htmlspecialchars($article['slug']) ?>' />

Результат: адреса становятся читаемыми и индексируемыми. Проблема: slug должен быть уникальным, при изменении заголовка нужна перегенерация.

Пример 4: Статья с мета-тегами (Open Graph, Twitter Cards)

Пример
<meta property='og:title' content='<?= htmlspecialchars($article['title']) ?>' />
<meta property='og:description' content='<?= htmlspecialchars(mb_substr(strip_tags($article['body']), 0, 200)) ?>' />
<meta property='og:image' content='<?= htmlspecialchars($article['image_url']) ?>' />
<meta name='twitter:card' content='summary_large_image' />

Результат: при репосте в соцсетях отображается красивый блок.

Пример 5: Статья с подсветкой синтаксиса (Prism.js)

В теле статьи используем блоки кода с тегами <pre><code class='language-php'>. Подключаем CSS/JS Prism.

Пример
// При выводе из БД 
$body = $article['body']; // уже содержит HTML с классами
?>
<div class='article-content'>
    <?= $body ?>
</div>
<script src='prism.js'></script>

Результат: код в статьях подсвечивается.

Статья на PHP - comments

En
Article php page (php)