Полный обзор построения собственной вики-системы на языке 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 на собственное решение с сохранением совместимости
  • Использование знакомого синтаксиса для пользователей
  • Быстрое прототипирование без разработки парсера
- Php use (оператор use в php)
- личный кабинет php (личный кабинет на php)
- Bitrix php admin (администрирование bitrix на php)

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

Система контроля версий страниц

Для отслеживания изменений создаётся таблица 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, легко изменять дизайн.

Вики на PHP - comments

En
Wiki index php (php)