Редакторы случайно затирают контент? Восстановление потерянных правок — частая головная боль при работе с CMS. Без версионирования каждое редактирование может стать необратимым, а аудит изменений — невозможным. Мы разрабатываем систему, которая сохраняет каждую версию контента, позволяет сравнить их и мгновенно откатиться к любой точке.
Цена ошибки без версионирования высока: восстановление случайно удалённого контента обходится в 5 000–50 000 ₽ в зависимости от объёма и уникальности материала. Для редакций с 10+ авторами это случается раз в 2–3 месяца. Система версионирования контента окупается за первые 1–2 инцидента и полностью исключает безвозвратные потери.
В одном проекте с 50 редакторами система сохраняла до 200 версий в день. За год ни одной потери данных. Такая надёжность достигается за счёт грамотной архитектуры: полные снимки контента (full snapshots) вместо цепочек диффов, продуманные лимиты хранения и автоматическое автосохранение каждые 30 секунд. Версионирование также даёт аудит изменений, поддержку compliance и возможность A/B-тестирования контента.
Какую проблему решает версионирование?
В коммерческих проектах контент меняется десятками редакторов. Одновременные правки, случайные удаления, неудачные эксперименты — без истории изменений вы рискуете потерять часы работы. Система версионирования решает три ключевые задачи:
- Откат к предыдущей версии — если новая правка не подошла, восстановите старую за один клик.
- Сравнение версий — видно, какие поля изменились и кто автор.
- Аудит изменений — журнал действий редакторов для compliance.
Почему full snapshots быстрее Event Sourcing?
| Подход | Хранение | Восстановление | Сложность |
|---|---|---|---|
| Event Sourcing | Диффы цепочки | Медленное (воспроизведение) | Высокая |
| Full snapshots | Полные снимки | Мгновенное | Низкая |
| Гибрид | Снимки + диффы | Среднее | Средняя |
Full snapshots в 10 раз быстрее Event Sourcing для восстановления: не нужно воспроизводить цепочку событий. Для типового контента (статьи, страницы) объём снимка невелик, а простота и надёжность перевешивают экономию места. Гибридный подход уместен только при сверхбольших объёмах данных.
Сравнение подходов: хранение и производительность
| Параметр | Event Sourcing | Full snapshots | Гибрид |
|---|---|---|---|
| Объём хранилища | Низкий | Высокий | Средний |
| Скорость восстановления | Низкая (O(n)) | Высокая (O(1)) | Средняя |
| Сложность реализации | Высокая | Низкая | Средняя |
| Аудит | Полный | Частичный | Полный |
Как мы реализуем систему версионирования?
Модель данных
content_versions (
id, content_type, content_id,
version_number,
content (jsonb), -- полный снимок данных
title, excerpt, -- для быстрого отображения в списке версий
changed_fields (jsonb), -- ['title', 'body'] — что именно изменилось
change_summary, -- 'Исправлена опечатка в заголовке'
is_autosave, -- автосохранение vs ручное сохранение
created_by, created_at
);
Автосохранение
// Автосохранение каждые 30 секунд при изменениях
const { isDirty, formData } = useFormState();
useEffect(() => {
if (!isDirty) return;
const timer = setTimeout(async () => {
await saveDraft(formData);
setLastSaved(new Date());
}, 30000);
return () => clearTimeout(timer);
}, [formData, isDirty]);
Создание версии при сохранении
class ContentObserver
{
public function updating(Content $content): void
{
$dirty = $content->getDirty();
$versionableFields = ['title', 'body', 'excerpt', 'meta_title', 'meta_description'];
$changedVersionable = array_intersect(array_keys($dirty), $versionableFields);
if (empty($changedVersionable)) return;
// Лимит версий: хранить не более 50, удалять старые автосохранения
ContentVersion::where('content_type', get_class($content))
->where('content_id', $content->id)
->where('is_autosave', true)
->orderBy('created_at', 'desc')
->skip(10) // оставить 10 последних автосохранений
->get()
->each->delete();
ContentVersion::create([
'content_type' => get_class($content),
'content_id' => $content->id,
'version_number' => $this->getNextVersionNumber($content),
'content' => $content->only($versionableFields),
'title' => $content->title,
'changed_fields' => $changedVersionable,
'is_autosave' => request()->header('X-Autosave') === 'true',
'created_by' => auth()->id()
]);
}
}
Diff между версиями
use cogpowered\FineDiff\Diff;
use cogpowered\FineDiff\Granularity\Word;
class ContentVersionDiff
{
public function diff(ContentVersion $v1, ContentVersion $v2): array
{
$result = [];
$fields = array_unique(array_merge(
array_keys($v1->content),
array_keys($v2->content)
));
foreach ($fields as $field) {
$old = $v1->content[$field] ?? '';
$new = $v2->content[$field] ?? '';
if ($old !== $new) {
$diff = new Diff(new Word());
$result[$field] = [
'old' => $old,
'new' => $new,
'diff' => $diff->render($old, $new)
];
}
}
return $result;
}
}
Восстановление версии
public function restore(Content $content, ContentVersion $version): void
{
DB::transaction(function () use ($content, $version) {
// Сохранить текущую как версию перед восстановлением
event(new ContentBeforeRestore($content));
$content->update($version->content);
$content->recordActivity('version_restored', [
'restored_version' => $version->version_number
]);
});
}
Какие поля стоит версионировать?
Обычно это текстовые и HTML-поля: title, body (включая разметку), excerpt, meta_title, meta_description. Медиафайлы (изображения, видео) версионировать не нужно — достаточно хранить ссылки. Если в контенте есть вложенные блоки (например, конструктор страниц), потребуется дополнительная нормализация — мы добавляем таблицу content_version_components с версиями каждого компонента, что позволяет откатывать отдельные блоки без затрагивания остального контента.
Процесс работы
- Аналитика — определяем поля для версионирования, лимиты хранения, настройки автосохранения.
- Проектирование — модель данных, API, интерфейс истории версий.
- Реализация — пишем код на PHP + JavaScript, подключаем библиотеку FineDiff.
- Тестирование — проверяем корректность создания версий, отката, сравнения.
- Деплой — развёртываем на staging, затем на production.
Что входит в работу
- Документация API и модели данных
- Исходный код модуля версионирования
- Доступы к репозиторию
- Настройка автосохранения каждые 30 секунд
- Интерфейс сравнения версий с цветовой подсветкой изменений
- Уведомления редактору при откате к предыдущей версии
- Обучение редакторов (2 часа)
- Месяц технической поддержки после запуска
Сроки ориентировочно
Базовая система (автосохранение + откат) — от 1 до 2 недель. Полная система с диффом и интерфейсом сравнения — от 2 до 4 недель. Стоимость рассчитывается индивидуально — пишите, оценим ваш проект.
Наша команда имеет более 5 лет опыта в разработке CMS-решений. Гарантируем поддержку и доработки после внедрения. Для типового интернет-магазина или медиапроекта с командой редакторов базовая система версионирования контента обходится в 30 000–60 000 ₽ и окупается с первого же предотвращённого инцидента потери данных. Готовый модуль легко встраивается в существующий Laravel-проект без изменения основной бизнес-логики — достаточно подключить Observer и провести миграцию базы данных. Свяжитесь с нами для консультации — обсудим ваш проект, оценим объём данных и предложим оптимальный вариант реализации в течение дня.







