Архитектура проекта
JournalPHP построен на Laravel 13 с многоуровневой архитектурой и чётким разделением ответственностей. Понимание структуры поможет быстро ориентироваться в коде.
Стек
| Слой | Технологии |
|---|---|
| Backend | PHP 8.4+, Laravel 13 |
| Frontend | Vue 3, Inertia.js 2, Vite, Tailwind CSS 3 |
| БД | MySQL 8 |
| Кэш / Сессии / Очереди | Redis |
| WebSocket | Laravel Reverb |
| Мониторинг очередей | Laravel Horizon |
| Поиск | Laravel Scout |
| Медиа | Local, Cloudinary или Yandex Cloud Object Storage |
Жизненный цикл запроса
HTTP-запрос
→ Middleware (auth, admin.access, CheckBanned, …)
→ Controller (валидация через Form Request)
→ Action (одна бизнес-операция)
→ Service / Repository
→ Inertia-ответ (Vue-компонент + данные)Inertia.js устраняет разрыв между серверным роутингом Laravel и клиентской навигацией Vue — страницы переключаются без полной перезагрузки.
Слои приложения
| Директория | Назначение |
|---|---|
app/Http/Controllers/ | Тонкие контроллеры: принять запрос, делегировать |
app/Http/Requests/ | Form Request — валидация входных данных |
app/Actions/ | Одна бизнес-операция на класс |
app/Services/ | Сложная доменная логика, интеграции |
app/Repositories/ | Доступ к данным через интерфейсы |
app/DTO/ | Типизированные объекты передачи данных |
app/Contracts/ | Интерфейсы общих возможностей |
app/Concerns/ | Трейты для подключения к моделям |
app/Enums/ | PHP-перечисления (типизированные константы) |
app/Models/ | Eloquent-модели |
app/Jobs/ | Фоновые задачи в очередях |
app/Events/ | Доменные события |
app/Listeners/ | Обработчики событий |
app/Observers/ | Хуки жизненного цикла моделей |
app/Notifications/ | Laravel-уведомления |
app/Policies/ | Авторизационные политики |
app/Console/Commands/ | Artisan-команды |
app/Providers/ | Сервис-провайдеры |
Actions
Actions (app/Actions/) — классы с единственной ответственностью. Каждый Action инкапсулирует одну операцию:
app/Actions/
├── Post/
│ ├── StorePostAction.php
│ ├── UnpublishPostAction.php
│ └── DeletePostAction.php
├── Comment/
│ ├── StoreCommentAction.php
│ └── DeleteBranchCommentAction.php
├── Subscription/
│ ├── FinalizeSubscriptionPaymentAction.php
│ └── CancelActiveSubscriptionAction.php
└── …Actions вызываются из контроллеров и других сервисов. Их легко тестировать изолированно.
Сервисы
Сервисы (app/Services/) реализуют многоэтапную логику:
| Директория | Назначение |
|---|---|
Feed/ | Pipeline-сборка лент (Feed.php + фильтры MyFeed, NewFeed, PopularFeed, EditorialFeed, OrderFeed) |
Payments/ | Абстракция платёжных провайдеров |
MetaBuilder/ | Генерация SEO, OpenGraph, JSON-LD |
Ai/ | Интеграция с DeepSeek (резюме, модерация); ключи в админке (/admin-panel/ai-settings), DEEPSEEK_* в .env — fallback |
ImageModeration/ | Yandex Vision API |
Uploader/ | Абстракция хранилища (Local, Cloudinary, Yandex Cloud Object Storage); драйвер выбирается в админке |
Subscription/ | Жизненный цикл подписок |
Comment/ | Доменная логика комментариев |
Post/ | Обработка блоков и опросов |
Репозитории
Репозитории (app/Repositories/) изолируют доступ к данным:
app/Repositories/
├── Post/
│ ├── Interfaces/PostRepositoryInterface.php
│ └── PostRepository.php
├── Config/
│ ├── Interfaces/GetMailConfigRepositoryInterface.php
│ └── GetMailConfigRepository.php
└── …Привязки интерфейс → реализация регистрируются в RepositoryServiceProvider.
Config-репозитории — единственный источник настроек из таблицы config. Значения кэшируются под ключом config.{key}.
Data Transfer Objects (DTO)
DTO (app/DTO/) — типизированные объекты для передачи данных между слоями вместо нетипизированных массивов:
// Вместо:
$data = ['title' => '...', 'blocks' => [...], 'category_id' => 1];
// Используется:
$data = new CreatePostDto(
title: '...',
intro: null,
category_id: 1,
reply_id: null,
blocks: '...',
is_publish: true,
is_official: false,
is_adult: false,
commenting_permissions: PostCommentingPermission::All,
);Контракты и трейты
Интерфейсы (app/Contracts/)
| Интерфейс | Возможность |
|---|---|
BookmarkableInterface | Закладки |
ReactionableInterface | Реакции |
ReportableInterface | Жалобы |
BannableInterface | Баны |
IgnorableInterface | Игнор-списки |
FollowableInterface | Подписки |
HasMedia | Медиавложения |
NotifiableInterface | Уведомления |
Uploader | Хранилище файлов |
Трейты (app/Concerns/)
Реализуют интерфейсы и подключаются к моделям:
Bannable, Followable, HasBookmarks, HasFollowers, HasIgnores, Ignorable, Reportable, Bookmarkable, Reactionable
Pipeline лент
Система лент (app/Services/Feed/Feed.php) использует Laravel Pipeline:
Feed
→ Pipeline
→ MyFeed (персональная)
→ NewFeed (новые)
→ PopularFeed (популярная)
→ EditorialFeed (редакторская)
→ OrderFeed (сортировка)
→ WithoutBlackList (игнорируемые пользователи, ключевые слова)
→ cursorPaginate → коллекция постовСменные драйверы (Driver Pattern)
Логика из AppServiceProvider вынесена в выделенные сервис-провайдеры: MailServiceProvider, MediaServiceProvider, AiServiceProvider, SlowQueryServiceProvider, ImageModerationServiceProvider. Каждый провайдер разрешает свой интерфейс в рантайме:
// AI-сервис — DeepSeek или null-драйвер
$this->app->bind(AiServiceInterface::class, fn() => ...);
// Модерация изображений — Yandex Vision или null
$this->app->bind(ImageModerationServiceInterface::class, fn() => ...);
// Хранилище файлов — Local / Cloudinary / Yandex Cloud
// Драйвер и учётные данные берутся из настроек БД (Админ-панель → Медиа)
// config/uploaders.php — реестр драйверов (класс-реализация для каждого имени)
$uploader = UploaderFactory::make($driver); // $driver читается из config/uploaders.phpДобавление нового провайдера не требует изменений в вызывающем коде. Для хранилища достаточно создать класс и добавить запись в config/uploaders.php.
Платёжные провайдеры
PaymentProviderManager и PaymentProviderFactory в app/Services/Payments/ реализуют Driver Pattern для платёжных провайдеров. Поддерживаются драйверы из PaymentProviderDriver: YooKassa и T-Bank. Добавление нового провайдера — реализация PaymentProviderInterface + регистрация в фабрике.
Маршруты
| Файл | Назначение |
|---|---|
routes/web.php | Публичные и аутентифицированные маршруты |
routes/admin.php | Маршруты Админ-панели (prefix: admin-panel) |
routes/channels.php | WebSocket-каналы Broadcasting |
routes/console.php | Artisan Scheduler |
Laravel Sanctum установлен, но отдельного файла routes/api.php нет — внешний API пока не выделен.
Ключевые модели
| Модель | Домен |
|---|---|
User | Аккаунты, роли, настройки; сообщества — User с type = community (UserType::Community) |
Post | Посты с JSON-блоками, версии, опросы; category_id → сообщество (User) |
Comment | Комментарии с ветками, soft delete |
Tag | Теги (полиморфная связь через tagables) |
SubscriptionPrice | Тарифные планы |
SubscriptionFeature | Feature-флаги подписки |
UserSubscription | Активные подписки |
SubscriptionPayment | Записи платежей |
Ban, ContentBan | Блокировки |
Complaint, ComplaintReason | Жалобы |
Chat, Message | Личные сообщения |
Notification | Уведомления |
Config | Настройки из БД |
Media | Загруженные файлы |
Advertisement | Рекламные блоки |
Структура БД
87+ миграций. Ключевые особенности:
- Soft delete на
users,comments,posts - ULID → BigInt конверсия первичных ключей (в истории миграций)
- Денормализованные счётчики (
post_counts,post_complaint_counts) для производительности - Полиморфные связи для тегов, реакций, закладок, жалоб
Инфраструктура
docker-compose.yml — разработка
docker-compose.prod.yml — продакшен
Dockerfile — образ для разработки (PHP 8.4 + Node)
Dockerfile.prod — образ для продакшена
docker/nginx/ — конфиги nginx
supervisord.conf — Horizon + Reverb