Редакторы случайно затирают контент? Восстановление потерянных правок — частая головная боль при работе с 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 и провести миграцию базы данных. Свяжитесь с нами для консультации — обсудим ваш проект, оценим объём данных и предложим оптимальный вариант реализации в течение дня.







