Реализация функционала добавления контентных страниц в PHP
Способы добавления страниц в PHP
Добавление новых страниц на сайт – одна из ключевых задач управления контентом. В PHP существует несколько подходов к реализации этого функционала. Выбор метода зависит от архитектуры проекта, требований к производительности и безопасности. Рассмотрим основные варианты.
Решение с использованием базы данных (рекомендуемое)
Этот способ предполагает хранение страниц в реляционной базе данных, например MySQL. Создается таблица pages с полями id, title, slug, content, created_at. Алгоритм: форма ввода, PHP-обработчик с PDO, вставка записи.
Пример формы (файл add_page.html):
<form method='post' action='add_page.php'>
<input type='text' name='title' placeholder='Заголовок' required>
<input type='text' name='slug' placeholder='URL (например, about)' required>
<textarea name='content' required></textarea>
<button type='submit'>Добавить страницу</button>
</form>
Add php page (добавить страницу php)
Обработчик add_page.php:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO pages (title, slug, content) VALUES (?, ?, ?)');
$stmt->execute([$_POST['title'], $_POST['slug'], $_POST['content']]);
header('Location: success.php');
Пояснения: подготовленный запрос защищает от SQL-инъекций. После вставки выполняется редирект.
Типичные проблемы:
- SQL инъекция при отсутствии экранирования. Решение – использование подготовленных выражений (PDO).
- Дублирование slug. Необходимо проверять уникальность перед вставкой.
- XSS атаки при выводе контента. При сохранении не фильтровать, а при выводе использовать htmlspecialchars.
Вариант 1: Хранение страниц в файловой системе
Как создать страницу без базы данных, сохраняя её в файл?
В этом методе каждая страница – отдельный PHP-файл в определённой директории. Форма принимает название файла и содержимое, обработчик создаёт файл с помощью file_put_contents.
<?php
$filename = $_POST['slug'] . '.php';
$content = '<?php // страница ' . $_POST['title'] . ' ?>' . PHP_EOL . $_POST['content'];
file_put_contents('pages/' . $filename, $content);
echo 'Страница создана';
Проблемы:
- Пользователь может вставить PHP-код, что приведёт к выполнению произвольного кода. Решение – отключать интерпретацию PHP в сохранённых файлах (например, хранить как .html или использовать фильтрацию).
- Конфликты имён файлов. Проверять существование с помощью file_exists.
- Права доступа – веб-сервер должен иметь право на запись.
Вариант 2: Использование шаблонизатора Twig
Как отделить логику от представления при добавлении страниц?
Twig позволяет создавать шаблоны, в которые передаются данные. Страница может сохраняться в БД как шаблон или как данные, затем рендериться.
<?php
require_once 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader);
// Сохраняем шаблон content.html.twig в БД или файл
$template = '<h1>{{ title }}</h1><p>{{ content }}</p>';
file_put_contents('templates/' . $slug . '.html.twig', $template);
// При выводе
echo $twig->render($slug . '.html.twig', ['title' => $title, 'content' => $content]);
Проблемы:
- Необходимость компиляции шаблонов при каждом изменении. Использовать кэш Twig.
- Сложность для простых проектов.
Вариант 3: Интеграция с WordPress
Как добавить страницу в WordPress через PHP-код?
WordPress предоставляет функцию wp_insert_post. Необходимо предварительно загрузить среду WordPress (wp-load.php).
<?php
require_once('wp-load.php');
$post_data = array(
'post_title' => 'Новая страница',
'post_content' => 'Текст страницы',
'post_status' => 'publish',
'post_type' => 'page'
);
$post_id = wp_insert_post($post_data);
if ($post_id) echo 'Страница создана с ID ' . $post_id;
Проблемы:
- Прямой вызов wp-load.php может конфликтовать с буферизацией вывода.
- Необходимость обновления постоянных ссылок (flush_rewrite_rules).
- Обработка мета-полей и произвольных таксономий.
Вариант 4: Использование фреймворка Laravel
Как добавить страницу через Eloquent ORM в Laravel?
Laravel предлагает миграции, модели и валидацию. Пример контроллера:
<?php
namespace App\Http\Controllers;
use App\Models\Page;
use Illuminate\Http\Request;
class PageController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:pages',
'content' => 'required|string'
]);
Page::create($validated);
return redirect('/pages')->with('success', 'Страница добавлена');
}
}
Проблемы:
- Необходимость настройки маршрутов и CSRF-защиты.
- Обработка ошибок валидации (возврат ошибок в JSON или сессию).
Вариант 5: REST API для добавления страниц
Как добавить страницу через внешний API-запрос?
Сервер принимает JSON, обрабатывает и возвращает ответ. Пример на чистом PHP:
<?php
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['title'])) {
http_response_code(400);
echo json_encode(['error' => 'Неверные данные']);
exit;
}
// сохранение в БД
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO pages (title, slug, content) VALUES (?, ?, ?)');
$stmt->execute([$input['title'], $input['slug'], $input['content']]);
echo json_encode(['id' => $pdo->lastInsertId(), 'status' => 'ok']);
Проблемы:
- Отсутствие аутентификации. Добавлять проверку API-ключа.
- Обработка CORS для кросс-доменных запросов.
- Ограничение размера запроса и защита от повторной отправки.
Расширенные примеры реализации
Пример 1: Продвинутая обработка формы с CSRF, валидацией и транзакцией
Форма содержит скрытое поле с CSRF-токеном из сессии. Обработчик проверяет токен, использует PDO-транзакцию для атомарности, проверяет уникальность slug перед вставкой.
// Форма (add_page.php)
<form method='post' action=''>
<input type='hidden' name='csrf_token' value='<?php echo $_SESSION['csrf_token']; ?>'>
<input type='text' name='title' required>
<input type='text' name='slug' required>
<textarea name='content'></textarea>
<button type='submit'>Добавить</button>
</form>
// Обработчик (тот же файл)
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF-токен неверный');
}
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
try {
$pdo->beginTransaction();
// Проверка уникальности slug
$check = $pdo->prepare('SELECT COUNT(*) FROM pages WHERE slug = ?');
$check->execute([$_POST['slug']]);
if ($check->fetchColumn() > 0) {
throw new Exception('Slug уже существует');
}
$stmt = $pdo->prepare('INSERT INTO pages (title, slug, content, created_at) VALUES (?, ?, ?, NOW())');
$stmt->execute([$_POST['title'], $_POST['slug'], $_POST['content']]);
$pdo->commit();
echo 'Страница создана с ID ' . $pdo->lastInsertId();
} catch (Exception $e) {
$pdo->rollBack();
echo 'Ошибка: ' . $e->getMessage();
}
}
Страница создана с ID 42
Пример 2: Автоматическая генерация уникального slug из заголовка
Функция транслитерации кириллицы в латиницу, затем проверка в БД и добавление суффикса при дубликате.
function generateSlug($string, $pdo) {
$slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $string), '-'));
// Транслитерация для кириллицы
$translit = array(
'а'=>'a','б'=>'b','в'=>'v','г'=>'g','д'=>'d','е'=>'e','ё'=>'yo','ж'=>'zh',
'з'=>'z','и'=>'i','й'=>'y','к'=>'k','л'=>'l','м'=>'m','н'=>'n','о'=>'o',
'п'=>'p','р'=>'r','с'=>'s','т'=>'t','у'=>'u','ф'=>'f','х'=>'kh','ц'=>'ts',
'ч'=>'ch','ш'=>'sh','щ'=>'shch','ы'=>'y','э'=>'e','ю'=>'yu','я'=>'ya'
);
$slug = strtr($string, $translit);
$slug = preg_replace('/[^A-Za-z0-9-]+/', '-', $slug);
$slug = trim($slug, '-');
$original = $slug;
$i = 0;
while (true) {
$check = $pdo->prepare('SELECT COUNT(*) FROM pages WHERE slug = ?');
$check->execute([$slug]);
if ($check->fetchColumn() == 0) break;
$i++;
$slug = $original . '-' . $i;
}
return $slug;
}
// Использование:
$slug = generateSlug($_POST['title'], $pdo);
При заголовке 'О нас' вернёт 'o-nas'
Пример 3: Сохранение страницы с поддержкой нескольких языков
Таблица pages содержит только id и created_at. Таблица page_translations содержит page_id, locale, title, content. Форма с выбором языка.
// Создание таблиц
CREATE TABLE pages (
id INT AUTO_INCREMENT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE page_translations (
id INT AUTO_INCREMENT PRIMARY KEY,
page_id INT NOT NULL,
locale VARCHAR(5) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE
);
// Обработчик
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO pages () VALUES ()');
$stmt->execute();
$pageId = $pdo->lastInsertId();
$stmt = $pdo->prepare('INSERT INTO page_translations (page_id, locale, title, content) VALUES (?, ?, ?, ?)');
foreach ($_POST['translations'] as $locale => $data) {
$stmt->execute([$pageId, $locale, $data['title'], $data['content']]);
}
$pdo->commit();
Страница 5 создана с переводами ru, en
Пример 4: Добавление страницы через API с аутентификацией по токену
API-ключ передается в заголовке Authorization. Сервер проверяет его в таблице api_keys, затем обрабатывает JSON.
<?php
header('Content-Type: application/json');
$apiKey = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$check = $pdo->prepare('SELECT id FROM api_keys WHERE `key` = ? AND active = 1');
$check->execute([$apiKey]);
if (!$check->fetch()) {
http_response_code(401);
echo json_encode(['error' => 'Неверный API-ключ']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['title'])) {
http_response_code(400);
echo json_encode(['error' => 'Отсутствует заголовок']);
exit;
}
$stmt = $pdo->prepare('INSERT INTO pages (title, slug, content) VALUES (?, ?, ?)');
$stmt->execute([$input['title'], $input['slug'] ?? '', $input['content'] ?? '']);
echo json_encode(['status' => 'ok', 'id' => $pdo->lastInsertId()]);
{"status":"ok","id":101}
Пример 5: Кэширование готовых страниц для ускорения вывода
При сохранении страницы обновляется кэш. При запросе сначала проверяется кэш-файл, и если он актуален, выводится без выполнения PHP.
// Функция отображения страницы с кэшем
function showPage($slug, $pdo) {
$cacheFile = __DIR__ . '/cache/' . $slug . '.html';
$cacheTime = 3600; // 1 час
// Если кэш существует и не устарел
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
readfile($cacheFile);
return;
}
// Иначе получаем из БД
$stmt = $pdo->prepare('SELECT title, content FROM pages WHERE slug = ?');
$stmt->execute([$slug]);
$page = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$page) {
http_response_code(404);
echo 'Страница не найдена';
return;
}
ob_start();
echo '<h1>' . htmlspecialchars($page['title']) . '</h1>';
echo '<div>' . $page['content'] . '</div>';
$html = ob_get_clean();
file_put_contents($cacheFile, $html);
echo $html;
}
// При создании/обновлении страницы нужно очищать кэш:
$cacheFile = __DIR__ . '/cache/' . $slug . '.html';
if (file_exists($cacheFile)) unlink($cacheFile);
Страница отображается из кэша, время загрузки < 0.01 сек