Архитектура проекта
JournalPHP — монолит на Laravel с фронтендом на Vue 3, связанным с сервером через Inertia.js.
Обзор
Сервер отдаёт страницы через Inertia: контроллер возвращает имя Vue-страницы и её данные, а переходы между страницами происходят без полной перезагрузки. Бизнес-логика вынесена из контроллеров в Actions, доступ к данным — в репозитории с интерфейсами, интеграции — в сервисы со сменными драйверами. Настройки платформы хранятся в базе данных, поэтому драйверы и ключи интеграций выбираются в админ-панели без правки .env.
Фоновая работа выполняется в том же приложении: очереди обрабатывает Laravel Horizon, WebSocket-сообщения отправляет Laravel Reverb, расписание задач запускает планировщик Laravel. Версии компонентов перечислены в разделе Системные требования.
Стек
| Слой | Технологии |
|---|---|
| Backend | PHP, Laravel |
| Frontend | Vue 3, Inertia.js, Vite, Tailwind CSS, Element Plus |
| База данных | MySQL |
| Кэш, сессии, очереди | Redis, Laravel Horizon |
| WebSocket | Laravel Reverb, Laravel Echo |
| Поиск | Laravel Scout: база данных, Algolia, Meilisearch |
Путь запроса
HTTP-запрос
→ Middleware (auth, admin.access, content-ban, has.subscription.feature, …)
→ Controller + Form Request (валидация)
→ Action (одна бизнес-операция)
→ Service / Repository
→ Inertia-ответ (Vue-страница и её данные) или JSONМаршруты сайта описаны в routes/web.php, маршруты админ-панели с префиксом admin-panel — в routes/admin.php, который подключается из web.php. Каналы WebSocket описаны в routes/channels.php. Расписание задач находится в app/Console/Kernel.php.
Структура app/
| Директория | Назначение |
|---|---|
Http/Controllers/ | Контроллеры: принимают запрос и делегируют работу |
Http/Middleware/ | Middleware |
Http/Requests/ | Form Request — валидация входных данных |
Http/Resources/ | API Resources — формирование ответов |
Actions/ | Одна бизнес-операция на класс |
Services/ | Доменная логика и интеграции |
Repositories/ | Доступ к данным через интерфейсы |
DTO/ | Типизированные объекты передачи данных |
Contracts/, Concerns/ | Интерфейсы общих возможностей моделей и трейты, которые их реализуют |
Enums/ | PHP-перечисления |
Models/ | Eloquent-модели |
Events/, Listeners/ | Доменные события и их обработчики |
Jobs/ | Фоновые задачи |
Notifications/ | Уведомления Laravel |
Observers/ | Хуки жизненного цикла моделей |
Policies/ | Правила авторизации |
Rules/ | Собственные правила валидации |
Helpers/ | Глобальные функции проверки функций подписки |
Console/Commands/ | Artisan-команды |
Providers/ | Сервис-провайдеры |
Фронтенд
resources/js/
├── Pages/ # Inertia-страницы сайта
├── components/ # Компоненты сайта
├── editor/ # Модули редактора постов
├── Shared/ # Общий layout и глобальные модальные окна
├── Admin/ # Страницы и компоненты админ-панели
├── app.js # Точка входа сайта
└── admin.js # Точка входа админ-панелиОсновные стили — resources/css/app.css, CSS-переменные темы — resources/css/variables.css, стили админ-панели — resources/css/admin.css. Tailwind настраивается в tailwind.config.cjs. Цвета по умолчанию для Админ-панель → Настройки → Сайт → Внешний вид заданы в config/appearance.php.
Actions
Action — класс с единственным публичным методом execute(), который выполняет одну операцию. Actions сгруппированы по доменам: app/Actions/Post/StorePostAction.php, app/Actions/Comment/StoreCommentAction.php, app/Actions/Search/ImportSearchIndexesAction.php. Их вызывают контроллеры, слушатели, задачи и другие Actions.
Репозитории
Репозиторий состоит из интерфейса в подкаталоге Interfaces/ и реализации рядом с ним:
app/Repositories/Post/
├── Interfaces/
│ └── GetPostBySlugRepositoryInterface.php
└── GetPostBySlugRepository.phpRepositoryServiceProvider находит такие пары и автоматически регистрирует привязку «интерфейс → реализация», если реализация называется как интерфейс без суффикса Interface.
Репозитории в app/Repositories/Config/ читают и записывают настройки из таблицы config. При изменении записи модель Config сбрасывает связанные кэши.
DTO
DTO в app/DTO/ передают данные между слоями вместо нетипизированных массивов, например CreatePostDto при создании поста.
Контракты и трейты
Общие возможности моделей описаны интерфейсами в app/Contracts/ и реализованы трейтами из app/Concerns/:
| Интерфейс | Трейт | Возможность |
|---|---|---|
BookmarkableInterface | Bookmarkable | Добавление в закладки |
ReactionableInterface | Reactionable | Реакции |
ReportableInterface | Reportable | Жалобы |
IgnorableInterface | Ignorable | Скрытие пользователем |
NotifiableInterface | Notifiable | Подписка на уведомления |
FollowableInterface | HasFollowers | Наличие подписчиков |
FollowerInterface | Followable | Подписки на пользователей и темы |
BannableInterface | Bannable | Баны |
HasNotificationsInterface | HasNotifications | Подписки пользователя на уведомления |
HasMedia | — | Медиавложения |
| — | HasBookmarks, HasIgnores | Закладки и скрытые объекты пользователя |
Ленты
Лента (app/Services/Feed/Feed.php) собирается через Laravel Pipeline:
Feed
→ MyFeed — посты из подписок
→ NewFeed — свежие посты
→ PopularFeed — фильтр по периоду
→ EditorialFeed — редакционные посты
→ OrderFeed — сортировка
→ WithoutBlackList — исключение скрытых пользователей, постов и ключевых слов
→ cursorPaginate(10)MyFeed, NewFeed и PopularFeed срабатывают только для своего типа ленты, EditorialFeed — при фильтре editorial. OrderFeed выбирает сортировку по типу ленты, WithoutBlackList применяется ко всем лентам авторизованного пользователя.
Сервисы
| Директория | Назначение |
|---|---|
Feed/ | Сборка лент |
Search/ | Поисковые драйверы: база данных, Algolia, Meilisearch |
Payments/ | Платёжные провайдеры: ЮKassa, T-Bank |
Subscription/ | Оплата и автопродление подписок |
Ai/ | AI-провайдеры и бот в комментариях |
Moderation/ | AI-модерация постов, комментариев и изображений |
Uploader/ | Хранилища файлов: локальный диск, Cloudinary, Yandex Cloud |
Gif/ | Поиск GIF: Tenor, Giphy |
MetaBuilder/ | Мета-теги, OpenGraph и JSON-LD страниц |
Seo/ | Чтение мета-данных внешних ссылок |
Post/ | Блоки постов, опросы, краткое изложение, счётчики |
Comment/ | Формирование ответов с комментариями |
User/ | Бейдж и избранное достижение пользователя |
Auth/ | OAuth и проверка токенов One Tap |
Advertisement/ | Подбор и вывод рекламных блоков |
SharedData/ | Общие данные для всех Inertia-страниц |
Admin/ | Разделы админ-панели |
Сменные драйверы
Интеграции, которые выбираются в админ-панели, устроены одинаково:
app/Services/Gif/
├── Contracts/GifServiceInterface.php — контракт
├── AbstractGifService.php — общий алгоритм драйверов
├── Drivers/ — реализации
│ ├── TenorGifService.php
│ └── GiphyGifService.php
└── GifManager.php — выбор и создание драйвера- Контракт: описывает, что умеет любой драйвер
- Абстрактный класс: содержит общую часть, драйвер реализует только отличия
- Менеджер: наследует
Illuminate\Support\Manager,getDefaultDriver()берёт драйвер из настроек, методыcreate{Name}Driver()создают драйверы - Enum: перечисляет драйверы и их названия для админ-панели
| Интеграция | Менеджер | Enum | Драйверы |
|---|---|---|---|
| AI | AiManager | AiProvider | DeepSeek, OpenAI, Gemini, YandexGPT |
| Поиск | SearchManager | SearchDriver | База данных, Algolia, Meilisearch |
| Поиск GIF | GifManager | GifDriver | Tenor, Giphy |
| Хранилище файлов | UploaderManager | UploaderDriver | Local, Cloudinary, Yandex Cloud |
| Модерация изображений | ImageModerationManager | ImageModerationDriver | Yandex Vision, без проверки |
| Платежи | PaymentManager | PaymentProviderDriver | ЮKassa, T-Bank |
DeepSeek, Gemini и YandexGPT используют общий драйвер OpenAiCompatibleService: они совместимы с OpenAI Chat Completions API. OpenAiService наследует его и отличается параметрами запроса.
Контракты AI, хранилища и модерации изображений регистрируются в сервис-провайдерах как scoped: экземпляр создаётся заново для каждого запроса и каждой задачи очереди, поэтому воркеры сразу используют драйвер, выбранный в админ-панели.
Поисковый драйвер реализует два интерфейса: SearchDriverInterface (провайдеры поиска постов и пользователей) и SearchIndexManagementInterface (проверка подключения и импорт индекса). ScoutConfigServiceProvider при загрузке приложения передаёт Scout драйвер и ключи из админ-панели.
Почта устроена иначе: MailServiceProvider подставляет настройки SMTP из админ-панели в конфигурацию mail при загрузке приложения, а транспорт выбирает встроенный MailManager Laravel. После сохранения настроек почты MailSettingsController выполняет queue:restart, и воркеры очередей перезапускаются с новыми настройками, см. Почта.
Поисковые индексы
Параметры индексов описаны в config/scout-indexes/:
| Файл | Содержимое |
|---|---|
algolia-posts.php, algolia-users.php | Поля поиска, ранжирование и фильтрация для Algolia |
meilisearch-posts.php, meilisearch-users.php | Поля поиска, фильтры, сортировка, правила ранжирования и допуск опечаток для Meilisearch |
В meilisearch-posts.php параметр rankingScoreThreshold (по умолчанию 0.5) отсекает слабые совпадения. После изменения файлов индекс синхронизируется заново — см. Поиск.
Модели
| Модель | Назначение |
|---|---|
User | Пользователи и темы: тема — это User с type = community |
Post | Посты: блоки в JSON, category_id — тема, reply_id — исходный пост для ответного |
PostVersionHistory | Версии постов |
Comment | Комментарии с ветками |
Tag, Tagable | Теги и их связи |
Reaction, ReactionItem | Реакции и их набор |
Achievement, UserAchievement | Достижения и их получение пользователями |
MonthlyRatingPlace | Итоговые места месячных топов |
Chat, Message | Личные сообщения |
Notification | Подписки пользователей на уведомления (таблица notification_subscriptions) |
Ban, ContentBan, ViolationBanRule | Баны, запреты на публикацию и правила автоматических запретов |
Complaint, ComplaintReason | Жалобы и их причины |
SubscriptionPrice, SubscriptionFeature, UserSubscription, SubscriptionPayment | Подписка |
Advertisement | Рекламные блоки |
Media | Загруженные файлы |
Config | Настройки платформы |
Role, UserRole, UserAdminSection | Роли и доступ к разделам админ-панели |
База данных
- Пользователи удаляются мягко; окончательное удаление аккаунта откладывается через поле
will_be_deleted_at - Счётчики постов хранятся в таблице
post_countsи пересчитываются командойphp artisan post-counts:refresh - Теги, реакции, закладки, жалобы и медиа привязываются через полиморфные связи
Инфраструктура
docker-compose.yml — локальное окружение
docker-compose.prod.yml — продакшен
Dockerfile — образ приложения для разработки
Dockerfile.prod — многоэтапная сборка: ассеты → PHP runtime → nginx
docker/entrypoint-*.sh — сценарии запуска контейнеров
docker/nginx/ — конфигурации nginx
supervisord.conf — PHP-FPM, Horizon, планировщик, Reverb