Полный обзор построения собственной вики-системы на языке PHP
Создание собственной вики-системы на PHP позволяет организовать корпоративную базу знаний, документацию или личную записную книжку без привязки к внешним сервисам. В этой статье рассматриваются несколько подходов к реализации, от простого файлового хранилища до полнофункциональной системы с базой данных. Каждый вариант сопровождается примерами кода, пояснениями и разбором типичных ошибок.
Основные подходы к созданию вики-системы на PHP
Эффективное решение: Markdown + JSON-файлы
Этот подход сочетает простоту файлового хранения с удобством разметки Markdown. Страницы сохраняются в виде JSON-файлов, что позволяет легко добавлять метаданные (заголовок, дату, автора). Для отображения используется парсер Markdown, например Parsedown.
Как создать минимальную вики, где страницы хранятся в файлах и отображаются через Markdown?
Структура проекта:
- pages/ – папка с JSON-файлами страниц (например, about.json)
- index.php – точка входа, обрабатывающая параметр ?page=slug
- Parsedown.php – библиотека для парсинга Markdown
Пример JSON-файла страницы:
{
"title": "О проекте",
"content": "# Добро пожаловать\n\nЭто **вики** на PHP, использующая Markdown.",
"updated_at": "2025-02-18 12:00:00"
}
обработка php (обработка данных в php)
Код index.php:
<?php
require 'Parsedown.php';
$slug = $_GET['page'] ?? 'main';
// Безопасная проверка: разрешены только буквы, цифры и дефис
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $slug)) {
die('Некорректный идентификатор страницы');
}
$file = __DIR__ . '/pages/' . $slug . '.json';
if (!file_exists($file)) {
http_response_code(404);
die('Страница не найдена');
}
$data = json_decode(file_get_contents($file), true);
if (!$data) {
die('Ошибка чтения страницы');
}
// Экранирование заголовка для защиты от XSS
$title = htmlspecialchars($data['title'], ENT_QUOTES, 'UTF-8');
$parsedown = new Parsedown();
$htmlContent = $parsedown->text($data['content']);
?>
<!DOCTYPE html>
<html>
<head><title><?= $title ?></title></head>
<body>
<h1><?= $title ?></h1>
<div><?= $htmlContent ?></div>
</body>
</html>
Php learning (обучение php)
Типичные ошибки и способы их решения:
- Directory Traversal – если не проверять slug, злоумышленник может загрузить ../etc/passwd. Решение – ограничить допустимые символы с помощью регулярного выражения или использовать белые списки.
- XSS через заголовок – прямое отображение $data['title'] без экранирования. Решение – htmlspecialchars().
- Проблемы с кодировкой – JSON должен быть в UTF-8, иначе json_decode вернет null. Решение – использовать mb_convert_encoding при необходимости.
Цели и случаи использования:
- Небольшие проекты до 100 страниц, высокая скорость развертывания
- Отсутствие базы данных – легко копировать на флешку или хостинг
- Учебная реализация для понимания принципов вики
Вариант: хранение страниц в MySQL
Как организовать хранение с возможностью поиска и версионирования?
База данных даёт удобные механизмы поиска, индексации и контроля версий. Создадим таблицу pages:
CREATE TABLE pages (
id INT AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(100) UNIQUE NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Php учебник (учебник по php)
Пример PHP-кода для получения и отображения страницы:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=wiki;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$slug = $_GET['page'] ?? 'main';
$stmt = $pdo->prepare('SELECT title, content FROM pages WHERE slug = ?');
$stmt->execute([$slug]);
$page = $stmt->fetch();
if (!$page) {
http_response_code(404);
die('Страница не найдена');
}
$title = htmlspecialchars($page['title'], ENT_QUOTES, 'UTF-8');
$parsedown = new Parsedown();
$htmlContent = $parsedown->text($page['content']);
?>
<h1><?= $title ?></h1>
<div><?= $htmlContent ?></div>
Apis php (api на php)
Для поиска используйте LIKE:
$search = $_GET['q'] ?? '';
$stmt = $pdo->prepare('SELECT slug, title FROM pages WHERE content LIKE ? OR title LIKE ?');
$stmt->execute(['%' . $search . '%', '%' . $search . '%']);
$results = $stmt->fetchAll();
Php categoryid (категории в php)
Проблемы и решения:
- SQL-инъекции – использование PDO с подготовленными запросами полностью решает проблему.
- Производительность LIKE – для больших объёмов используйте полнотекстовый индекс (FULLTEXT) и оператор MATCH ... AGAINST.
- Версионирование – создайте отдельную таблицу page_revisions с копией данных при каждом обновлении.
Цели использования:
- Проекты с десятками тысяч страниц и активным поиском
- Необходимость одновременного доступа нескольких редакторов
- Требование аудита изменений (история версий)
Вариант: простая вики на текстовых файлах с собственной разметкой
Как сделать минимальную вики без внешних зависимостей и базы данных?
Страницы хранятся как обычные текстовые файлы с расширением .wiki. Разметка обрабатывается простыми PHP-функциями: ссылки в формате {{PageName}}, заголовки с =.
Пример файла home.wiki:
= Добро пожаловать =
Это простая вики. Ссылка на [Справка|{{help}}].
Html javascript php (интеграция html, javascript и php)
Функция парсинга:
function parseWiki($text) {
// Обработка заголовков
$text = preg_replace('/^= (.+) =$/m', '$1
', $text);
// Обработка ссылок {{PageName}} или [Текст|{{PageName}}]
$text = preg_replace_callback('/\[([^|]+)\|\{\{([^}]+)\}\}\]/', function($m) {
$slug = urlencode($m[2]);
return '' . htmlspecialchars($m[1]) . '';
}, $text);
return nl2br($text);
}
Index php post (обработка post-запросов в php)
Полный код index.php:
<?php
$slug = $_GET['page'] ?? 'home';
$file = __DIR__ . '/pages/' . $slug . '.wiki';
if (!file_exists($file)) {
http_response_code(404);
die('Страница не найдена');
}
$content = file_get_contents($file);
try {
$html = parseWiki($content);
} catch (Exception $e) {
die('Ошибка парсинга: ' . $e->getMessage());
}
?>
<!DOCTYPE html>
<html><body><div><?= $html ?></div></body></html>
Index php page ru (создание index.php страницы)
Типичные ошибки:
- Неэкранированный вывод – если файл содержит HTML-теги, они выполнятся. Решение – пропускать через htmlspecialchars перед парсингом или использовать парсер, который экранирует всё, кроме разрешённой разметки.
- Сложность поддержки – при расширении разметки код становится запутанным. Решение – использовать готовый парсер Markdown или Wikitext.
- Проблемы с кодировкой – файлы должны быть в UTF-8 без BOM.
Цели:
- Обучающие проекты, демонстрация базовых принципов
- Максимально легковесная система (нет библиотек, только PHP)
- Эксперименты с собственным синтаксисом
Вариант: использование библиотеки php-wikitext (MediaWiki парсер)
Как поддержать синтаксис MediaWiki в собственной вики без написания парсера с нуля?
Библиотека mediawiki/parser (пакет PEAR) позволяет преобразовывать вики-текст в HTML. Установка через Composer:
composer require mediawiki/parser
Php javascript css (javascript и css в php)
Пример использования:
<?php
require 'vendor/autoload.php';
$wikiText = file_get_contents('pages/home.wikitext');
$parser = new MediaWiki\Parser\Parser();
$html = $parser->parse($wikiText);
?>
<div><?= $html ?></div>
Проблемы:
- Зависимости – библиотека требует несколько PEAR-пакетов и может быть сложна в установке на современных версиях PHP.
- Производительность – парсинг большого объёма текста может быть медленным.
- Безопасность – вывод не экранируется автоматически, необходимо дополнительно обрабатывать.
Цели:
- Миграция с MediaWiki на собственное решение с сохранением совместимости
- Использование знакомого синтаксиса для пользователей
- Быстрое прототипирование без разработки парсера
Расширенные примеры реализации
Система контроля версий страниц
Для отслеживания изменений создаётся таблица page_revisions с полями: revision_id, page_id, content, timestamp, author. При каждом сохранении текущий контент копируется в таблицу ревизий, после чего обновляется основная запись.
Пример структуры таблицы и PHP-кода:
CREATE TABLE page_revisions (
id INT AUTO_INCREMENT PRIMARY KEY,
page_id INT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
author VARCHAR(100),
FOREIGN KEY (page_id) REFERENCES pages(id) ON DELETE CASCADE
);
<?php
function savePageRevision(PDO $pdo, $pageId, $content, $author) {
// Сохраняем текущую версию
$stmt = $pdo->prepare('INSERT INTO page_revisions (page_id, content, author) VALUES (?, ?, ?)');
$stmt->execute([$pageId, $content, $author]);
// Обновляем основную запись
$stmtUpdate = $pdo->prepare('UPDATE pages SET content = ?, updated_at = NOW() WHERE id = ?');
$stmtUpdate->execute([$content, $pageId]);
}
// Пример вызова
$pdo = new PDO(...);
$pageId = 1;
$newContent = '# Новая версия';
$author = 'admin';
savePageRevision($pdo, $pageId, $newContent, $author);
?>
Результат: при запросе истории изменений можно получить список всех ревизий для страницы:
SELECT r.id, r.created_at, r.author FROM page_revisions r WHERE r.page_id = 1 ORDER BY r.created_at DESC;
Полнотекстовый поиск с использованием MySQL FULLTEXT
Альтернатива оператору LIKE – полнотекстовый индекс. Создаётся на столбцах title и content, после чего запросы выполняются с помощью MATCH ... AGAINST.
ALTER TABLE pages ADD FULLTEXT INDEX ft_pages (title, content);
<?php
$search = $_GET['q'] ?? '';
$stmt = $pdo->prepare("SELECT slug, title, MATCH(title, content) AGAINST (? IN BOOLEAN MODE) AS relevance FROM pages WHERE MATCH(title, content) AGAINST (? IN BOOLEAN MODE) ORDER BY relevance DESC");
$stmt->execute([$search, $search]);
$results = $stmt->fetchAll();
?>
Результат: выдаются страницы, отсортированные по релевантности. Пример вывода:
- documentation (релевантность: 2.5) - about (релевантность: 1.0)
REST API для управления страницами
API позволяет внешним приложениям получать, создавать и обновлять страницы. Используется формат JSON и токен авторизации.
<?php
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
$slug = $_GET['page'] ?? '';
$pdo = new PDO(...);
if ($method === 'GET') {
if ($slug) {
$stmt = $pdo->prepare('SELECT slug, title, content FROM pages WHERE slug = ?');
$stmt->execute([$slug]);
$page = $stmt->fetch();
echo json_encode($page ?: ['error' => 'Not found']);
} else {
$stmt = $pdo->query('SELECT slug, title FROM pages');
echo json_encode($stmt->fetchAll());
}
} elseif ($method === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
// Проверка токена и создание/обновление
$stmt = $pdo->prepare('INSERT INTO pages (slug, title, content) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE title = VALUES(title), content = VALUES(content)');
$stmt->execute([$input['slug'], $input['title'], $input['content']]);
echo json_encode(['status' => 'ok']);
}
?>
Пример запроса с помощью curl:
curl -X GET "http://wiki.local/api.php?page=home"
# Ответ: {"slug":"home","title":"Home","content":"# Welcome"}
curl -X POST -H "Content-Type: application/json" -d '{"slug":"test","title":"Test","content":"Test page"}' "http://wiki.local/api.php"
# Ответ: {"status":"ok"}
Использование шаблонов Twig для отделения представления
Установка Twig через Composer и интеграция с вики:
composer require twig/twig
<?php
require 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
$twig = new \Twig\Environment($loader, ['cache' => __DIR__ . '/cache']);
$pageData = []; // данные страницы
echo $twig->render('page.html', [
'title' => $pageData['title'],
'content' => $pageData['htmlContent'],
'isEditable' => true
]);
?>
Шаблон templates/page.html:
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
<h1>{{ title }}</h1>
<div>{{ content|raw }}</div>
{% if isEditable %}
<a href="?edit={{ slug }}">Редактировать</a>
{% endif %}
</body>
</html>
Результат: чистый HTML, легко изменять дизайн.