Реализация страницы статьи: от файлов до базы данных
Вывод статьи на 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)
Пояснения шагов:
- Получение идентификатора статьи из URL с приведением к целому числу.
- Подключение к базе данных через PDO с указанием кодировки utf8.
- Подготовленный запрос предотвращает SQL-инъекции.
- Если статья не найдена, возвращается код 404 и сообщение.
- Вывод данных с использованием
htmlspecialcharsдля защиты от XSS. - Функция
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>Результат: код в статьях подсвечивается.