Как мы создали универсальную систему комментариев и рейтингов с полиморфными связями в Laravel

Привет, Хабр! Сегодня хочу поделиться опытом создания универсальной системы для работы с пользовательским контентом, которая позволила нам избежать дублирования кода и создать масштабируемое решение.

Проблема

При разработке образовательной платформы столкнулись с типичной задачей: нужно было добавить комментарии, рейтинги и счетчики просмотров для разных типов контента - статей и паттернов проектирования. Первое желание - скопировать код. Но мы пошли другим путем.

Решение: полиморфные отношения

Вместо создания отдельных таблиц pattern_comments, post_comments и т.д., мы использовали полиморфные связи Laravel:

Schema::create('comments', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->morphs('commentable'); // magic happens here
    $table->foreignId('user_id');
    $table->text('content');
    // ...
});

Трейты для переиспользования

Создали трейты, которые можно подключить к любой модели:

trait HasComments
{
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Результаты

  • -70% дублированного кода

  • Единая точка для исправления багов

  • Легкое добавление новых типов контента

Интересные находки

  1. Вложенность комментариев: ограничили глубиной 3 уровня для лучшего UX

  2. Оптимизация запросов: использовали eager loading для replies

  3. Real-time обновления: добавили через websockets (отдельная история)

Frontend на Next.js 15

Для фронтенда создали универсальный компонент с поддержкой:

  • Оптимистичных обновлений

  • Бесконечной прокрутки

  • Редактирования на месте

const { comments, createComment, toggleLike } = useComments({
    entityType: 'patterns',
    entityId: patternId
});

Выводы

Полиморфные связи - мощный инструмент для создания гибких систем. Да, есть overhead на начальном этапе, но выигрыш в долгосрочной перспективе огромный.


P.S. Кстати, если интересуетесь паттернами проектирования, у нас есть проект с интерактивными примерами и визуализацией архитектурных решений. А в телеграм-канале делимся опытом применения паттернов в реальных проектах и обсуждаем архитектурные решения.

Код из статьи использован в production на упомянутых ресурсах, так что все примеры боевые :)

Комментарии (3)


  1. mistergonza
    25.06.2025 10:26

    Извините, но пост выглядит конечно как сообщение вида: я покакал покушал. Что конечно не отменяет полезность полиморфных связей.


  1. FanatPHP
    25.06.2025 10:26

    А можно вас попросить рассказать подробнее про эти самые полиморфные отношения? А то статья, которая сводится к строчке "вот тут происходит вся магия", вызывает некоторое разочарование. И наводит на мысли о рекламном характере. При том что рекламируемый проект явно интересный, но тогда уж и стоило бы писать статью именно о нём?


  1. roxblnfk
    25.06.2025 10:26

    выигрыш в долгосрочной перспективе огромный

    Стоило бы уточнить, что у морф-связей есть большой недостаток: невозможность прокинуть Foreign Keys, поскольку внешняя таблица определяется не статически, а дискриминатором.

    Могу предположить, что огромный выигрыш заключается в отсутствии необходимости дублировать код комментариев/рейтингов для разных сущностей. Но тогда возникает другой вопрос:

    Почему не использовали STI или JTI? Разница с морфами: нужен дополнительный класс на каждую внешнюю таблицу (что может быть даже плюсом, ведь можно кое что местами кастомизировать), но зато будут FK и типизация в сущностях.