Розробка workflow-системи для публікації контенту на Laravel

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка workflow-системи для публікації контенту на Laravel
Складний
від 1 тижня до 3 місяців
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1359
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    957
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Проблема хаотичної публікації

В одній великій медіакомпанії контент-менеджер випадково опублікував чернетку з конфіденційними даними — це призвело до витоку та штрафу на півмільйона рублів. Такі інциденти — не рідкість, коли немає системи контролю версій та прав. Ми розробили 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: від аудиту до деплою

Процес складається з шести етапів:

  1. Аудит поточних процесів — інтерв'ю з редакторами, аналіз логів, карта статусів.
  2. Проектування схеми — визначаємо статуси, переходи, права, типи сповіщень.
  3. Розробка на Laravel — реалізація класів Workflow, подій, слухачів.
  4. Інтеграція з існуючою CMS — ставимо API, обгортаємо існуючий CRUD.
  5. Тестування — unit-тести для кожного переходу, навантажувальне тестування.
  6. Деплой та навчання — ролл-аут + сесія з редакторами.

Терміни: від 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

Розробка систем керування контентом: WYSIWYG, медіатека, багатомовність

Ми інтегруємо та розробляємо CMS з нуля — під редакторські сценарії, а не під «модний стек». Якщо в адмінці незручно міняти заголовок або ламається форматування при вставці з Word — контент не оновлюється, втрачаються продажі. Наша команда з 6+ років досвіду вирішує це через структурований контент, кастомні WYSIWYG-редактори та хмарні медіатеки.

Коли headless CMS виправдана, а коли — ні

Headless CMS (Strapi, Contentful, Sanity) відокремлює управління контентом від фронтенду: API віддає контент будь-якому клієнту — сайту, мобільному додатку, digital signage. Вибір для омніканальних проєктів і коли фронтенд на React/Vue/Next.js. Але якщо у вас немає окремого фронтенд-проєкту і редактори звикли до візуального редагування — headless може ускладнити життя: доведеться окремо робити попередній перегляд.

Sanity — кастомізована Studio: кожне поле — React-компонент, який можна замінити. Portable Text (формат для rich content) портується в будь-який рендерер. Для складних редакторських workflow — найкращий вибір. Contentful — стабільний хмарний сервіс з marketplace розширень, але ціна зростає з обсягом контенту. Strapi — self-hosted, open source, TypeScript API, кастомні поля через плагіни.

Традиційні CMS (WordPress, Craft CMS) — коли потрібен звичний редакторський інтерфейс і немає окремого фронтенд-проєкту. Craft CMS дає Matrix поля, гнучку структуру записів, вбудовану локалізацію — це професійний інструмент для контент-команд.

Як ми будуємо WYSIWYG-редактор, який не ламає верстку

Редактор — окрема інженерна задача, не просто <textarea>. Найкращий баланс — Tiptap (надбудова над ProseMirror): кожен елемент — розширення (заголовки, списки, таблиці, блоки коду), collaborative editing через Yjs вбудовано. Lexical (від Meta) — продуктивніший, але складніший у налаштуванні. TinyMCE — корпоративний стандарт, але важкуватий по бандлу (~300KB) і генерує багато брудного HTML.

Головна проблема — вставка з Word. &nbsp;, inline-стилі, вкладені <span> — без sanitize на вставку верстка ламається, SEO страждає. Ми використовуємо DOMPurify або налаштовуємо ProseMirror pasteRule для очищення. Результат — чистий HTML, який не змінюється при редизайні.

Медіатека: від завантаження до CDN

Завантажувати файли через <input type="file"> на диск сервера — антипатерн. Диск переповниться, масштабування неможливо, CDN не підключити. Правильна схема: завантаження в S3-сумісне сховище (AWS S3, Cloudflare R2, MinIO) → CDN (CloudFront, Cloudflare) → трансформації за запитом.

Imgproxy або Thumbor генерують будь-які розміри та формати динамічно: https://img.example.com/resize:800:600/format:webp/plain/s3://bucket/photo.jpg. Оригінал зберігається один раз, похідні не займають місце. Cloudflare Images — managed-сервіс.

Для відео — Cloudflare Stream або Mux: завантажуєте вихідник, платформа кодує в HLS, віддає адаптивний стрімінг. Без цього відео важить 500MB і завантажується цілком.

Що входить в розробку медіатеки

Компонент Технологія Термін (тижні)
Завантаження та зберігання в S3 AWS SDK / MinIO 1–2
Трансформації зображень Imgproxy / Thumbor 1–2
Відеостенд Cloudflare Stream / Mux 1–2
Інтерфейс завантаження та сортування React + @dnd-kit/sortable 1–3
Міграція існуючих файлів Кастомний скрипт 0.5–1

Структурований контент vs free-form HTML

Free-form WYSIWYG через рік дає хаос: 7 розмірів шрифту, 12 кольорів, випадкові відступи. Редизайн без ручного чищення неможливий. Структурований контент — замість «як воно виглядає» зберігаємо «що це є». Не <p style="font-size:24px; color:red">Важно!</p>, а тип блоку callout з параметром variant: warning. CMS зберігає структуру, фронтенд вирішує, як рендерити. Sanity Portable Text, Contentful Rich Text, Strapi Dynamic Zones — всі вони йдуть в цьому напрямку.

Чи варто впроваджувати структурований контент?

Процес роботи

  1. Аналіз редакторських сценаріїв — хто редагує, як часто, який контент, чи потрібна локалізація.
  2. Вибір CMS під сценарії, а не по трендах.
  3. Проектування контент-моделі — типи записів, поля, зв'язки.
  4. Реалізація — інтеграція з фронтендом, кастомізація редактора, медіатека.
  5. Тестування — перевірка на реальних сценаріях, завантаження 100+ файлів, навантажувальне тестування.
  6. Деплой та документація — інструкція для редакторів, опис API, доступи.

Строки та бюджет

Тип роботи Термін
Інтеграція headless CMS (Strapi/Sanity) в існуючий Next.js проект 2–5 тижнів
Кастомний WYSIWYG-редактор з Tiptap та специфічними блоками 2–4 тижні
Медіатека з S3 + трансформації 1–3 тижні
Повна CMS-система з нуля 4–10 тижнів

Бюджет розраховується індивідуально після аудиту. Зв'яжіться з нами — оцінимо ваш проєкт за один день.

Що ви отримаєте після завершення

  • Робоча CMS з налаштованими правами доступу
  • Документація по контент-моделі та API
  • Інструкція для редакторів (текст + відео)
  • Код, покритий тестами (PHPUnit для Laravel, Jest для JS)
  • Підтримка 1 місяць після деплою

Наш досвід

6 років на ринку, 40+ виконаних проєктів. Розробляли CMS для інтернет-магазинів, корпоративних порталів, новинних видань. Використовуємо ліцензійне ПЗ (sentry.io, sonarcloud) — гарантуємо якість коду.

Джерело: внутрішня статистика проєктів за 2018–2024 рр.

Детальніше про WYSIWYG-редактори читайте на Wikipedia.

Залишилися питання?

Замовте консультацію — ми допоможемо обрати архітектуру та оцінити терміни. Отримайте пропозицію протягом 2 робочих днів.