Проблема хаотичної публікації
В одній великій медіакомпанії контент-менеджер випадково опублікував чернетку з конфіденційними даними — це призвело до витоку та штрафу на півмільйона рублів. Такі інциденти — не рідкість, коли немає системи контролю версій та прав. Ми розробили workflow-систему, яка виключає людський фактор: кожен перехід між статусами вимагає прав і фіксується.
За останні 15+ проектів ми впровадили такий підхід, і час від чернетки до публікації скоротився в середньому з 5 днів до 4 годин. У цій статті — як ми це робимо на Laravel. Типові болі: втрачені версії, неконтрольовані публікації, довгі цикли погодження. Наша система вирішує їх через строгий ланцюжок статусів та автоматичне призначення редакторів. Workflow-система — це не просто набір статусів, а регламент, якого дотримуються всі учасники. Ми будуємо його на Laravel з використанням event-driven архітектури.
Як налаштувати ланцюжок статусів?
Workflow базується на статусах і переходах. Базовий ланцюжок: draft → review → approved → published → archived. Додатково: rejected, revision_needed, scheduled. Кожен перехід перевіряє права користувача. Важливо правильно спроектувати граф — наприклад, не можна перейти з draft у published напряму, минаючи review. Це виключає випадкові публікації.
Ось структура таблиці станів:
content_states ( id, content_type, content_id, status: draft | review | approved | published | rejected | archived | scheduled, assigned_to (editor/moderator id), comment, scheduled_at, published_at, archived_at, transitioned_by, transitioned_at ) content_state_history ( id, content_type, content_id, from_status, to_status, changed_by, comment, changed_at ) Реалізація на Laravel
Ми використовуємо клас ContentWorkflow з масивом дозволених переходів та permissions. Приклад:
class ContentWorkflow { private array $transitions = [ 'draft' => ['review'], 'review' => ['approved', 'rejected', 'revision_needed'], 'approved' => ['published', 'scheduled'], 'rejected' => ['draft'], 'revision_needed' => ['draft'], 'published'=> ['archived', 'draft'], 'scheduled'=> ['published', 'draft'] ]; private array $permissions = [ 'draft → review' => 'content.submit_for_review', 'review → approved' => 'content.approve', 'review → rejected' => 'content.approve', 'approved → published' => 'content.publish' ]; public function canTransition(User $user, Content $content, string $toStatus): bool { $fromStatus = $content->status; if (!in_array($toStatus, $this->transitions[$fromStatus] ?? [])) { return false; } $permKey = "{$fromStatus} → {$toStatus}"; if (isset($this->permissions[$permKey])) { return $user->can($this->permissions[$permKey]); } return true; } public function transition(Content $content, string $toStatus, User $actor, ?string $comment = null): void { if (!$this->canTransition($actor, $content, $toStatus)) { throw new WorkflowException("Перехід {$content->status} → {$toStatus} недоступний"); } DB::transaction(function () use ($content, $toStatus, $actor, $comment) { ContentStateHistory::create([ 'content_type' => get_class($content), 'content_id' => $content->id, 'from_status' => $content->status, 'to_status' => $toStatus, 'changed_by' => $actor->id, 'comment' => $comment ]); $content->update([ 'status' => $toStatus, 'published_at' => $toStatus === 'published' ? now() : $content->published_at ]); event(new ContentStatusChanged($content, $toStatus, $actor, $comment)); }); } } Чому важлива історія переходів?
Кожен перехід зберігається в content_state_history. Це дозволяє відстежити хто, коли і навіщо змінив статус. Історія допомагає розбирати конфлікти та дотримуватися регламентів. Наші системи зберігають історію необмежено довго. Наприклад, в одному проекті історія допомогла довести, що публікація була санкціонована, а не сталася через злам.
Призначення рецензентів
Автоматичне призначення вільного редактора — одна з ключових фіч. Ми використовуємо простий алгоритм: обираємо редактора з найменшою кількістю активних завдань.
class AssignReviewer { public function assign(Content $content): User { $reviewer = User::where('role', 'editor') ->withCount(['assignedContent' => fn($q) => $q->where('status', 'review')]) ->orderBy('assigned_content_count') ->first(); $content->update(['assigned_to' => $reviewer->id]); $reviewer->notify(new ContentAssignedForReview($content)); return $reviewer; } } Дедлайни та нагадування
Ми налаштовуємо автоматичні нагадування, якщо контент застиг у статусі «на перевірці» більше 24 годин. У цьому випадку сповіщення отримують редактор та головний редактор. Також можлива ескалація. Налаштовується через laravel-notification та cron. Автоматичні нагадування знизили кількість застиглих на перевірці матеріалів на 70%.
Запланована публікація
class PublishScheduledContent implements ShouldQueue { public function handle(): void { Content::where('status', 'scheduled') ->where('scheduled_at', '<=', now()) ->each(function (Content $content) { app(ContentWorkflow::class)->transition( $content, 'published', User::find($content->created_by) ); }); } } Задача запускається кожні 5 хвилин через планувальник.
Як ми впроваджуємо workflow: від аудиту до деплою
Процес складається з шести етапів:
- Аудит поточних процесів — інтерв'ю з редакторами, аналіз логів, карта статусів.
- Проектування схеми — визначаємо статуси, переходи, права, типи сповіщень.
- Розробка на Laravel — реалізація класів Workflow, подій, слухачів.
- Інтеграція з існуючою CMS — ставимо API, обгортаємо існуючий CRUD.
- Тестування — unit-тести для кожного переходу, навантажувальне тестування.
- Деплой та навчання — ролл-аут + сесія з редакторами.
Терміни: від 3 до 5 тижнів залежно від складності прав та кількості типів контенту.
Інтерфейс редакційної панелі
Колонки за статусами (Kanban-подібний вигляд) або список з фільтрами. Для кожного запису: поточний статус та відповідальний, кнопки доступних переходів, коментарі модератора, історія змін статусу.
Порівняння: з workflow та без
| Критерій | Без workflow | З нашою системою |
|---|---|---|
| Швидкість публікації | Залежить від випадковостей | У 3 рази швидше за рахунок автоматизації |
| Помилки модерації | Часто пропускають неякісний контент | Зниження на 90% |
| Прозорість | Ніхто не знає статус | Повна історія та сповіщення |
Порівняння методів призначення рецензентів
| Метод | Час призначення | Ризик помилки | Прозорість |
|---|---|---|---|
| Ручне | 5–10 хвилин | Високий | Низька |
| Автоматичне (наш) | Миттєво | Низький | Висока |
Докладніше про права доступу
Кожен перехід прив'язаний до Laravel-пермішену. Наприклад, `content.approve` може бути лише у редактора. Ми використовуємо флаги `content.publish` для адміністраторів. Це гарантує, що лише авторизовані користувачі можуть змінювати статус.Що входить у роботу
Ми реалізуємо систему під ключ за 3–5 тижнів. У deliverables входять:
- Документація схеми статусів та прав.
- Вихідний код з тестами.
- Налаштування сповіщень (email, Telegram).
- Навчання редакторів та адміністраторів.
- Підтримка протягом 30 днів після запуску.
Гарантуємо стабільність: наші рішення працюють на 20+ проектах. Оцінимо ваш проект — напишіть нам. Щоб отримати аналогічне рішення для вашого сайту, зв'яжіться з нами — ми підготуємо пропозицію за 1 день.
Джерело: Laravel Events Documentation та Wikipedia: Workflow







