Architecture Overview
JournalPHP is built on Laravel 13 with a layered architecture and clear separation of concerns. Understanding the structure helps developers navigate the codebase quickly.
Stack
| Layer | Technologies |
|---|---|
| Backend | PHP 8.4+, Laravel 13 |
| Frontend | Vue 3, Inertia.js 2, Vite, Tailwind CSS 3 |
| Database | MySQL 8 |
| Cache / Sessions / Queues | Redis |
| WebSocket | Laravel Reverb |
| Queue Monitoring | Laravel Horizon |
| Search | Laravel Scout |
| Media | Local, Cloudinary, or Yandex Cloud Object Storage |
Request Lifecycle
HTTP Request
→ Middleware (auth, admin.access, CheckBanned, …)
→ Controller (validation via Form Request)
→ Action (single business operation)
→ Service / Repository
→ Inertia Response (Vue component + data)Inertia.js bridges Laravel's server-side routing and Vue's client navigation — pages switch without full reloads.
Application Layers
| Directory | Purpose |
|---|---|
app/Http/Controllers/ | Thin controllers: accept request, delegate |
app/Http/Requests/ | Form Request — input validation |
app/Actions/ | One business operation per class |
app/Services/ | Complex domain logic, integrations |
app/Repositories/ | Data access through interfaces |
app/DTO/ | Typed inter-layer data transfer objects |
app/Contracts/ | Shared capability interfaces |
app/Concerns/ | Traits mixed into models |
app/Enums/ | PHP enumerations (typed constants) |
app/Models/ | Eloquent models |
app/Jobs/ | Queued background tasks |
app/Events/ | Domain events |
app/Listeners/ | Event handlers |
app/Observers/ | Model lifecycle hooks |
app/Notifications/ | Laravel notifications |
app/Policies/ | Authorization policies |
app/Console/Commands/ | Artisan commands |
app/Providers/ | Service providers |
Actions
Actions (app/Actions/) — single-responsibility classes:
app/Actions/
├── Post/
│ ├── StorePostAction.php
│ ├── UnpublishPostAction.php
│ └── DeletePostAction.php
├── Comment/
│ ├── StoreCommentAction.php
│ └── DeleteBranchCommentAction.php
├── Subscription/
│ ├── FinalizeSubscriptionPaymentAction.php
│ └── CancelActiveSubscriptionAction.php
└── …Actions are called from controllers and other services. They are easy to test in isolation.
Services
Services (app/Services/) implement multi-step logic:
| Directory | Purpose |
|---|---|
Feed/ | Pipeline-based feed assembly (Feed.php + MyFeed, NewFeed, PopularFeed, EditorialFeed, OrderFeed filters) |
Payments/ | Payment gateway abstraction |
MetaBuilder/ | SEO, OpenGraph, JSON-LD generation |
Ai/ | DeepSeek integration (summaries, moderation); keys in admin (/admin-panel/ai-settings), DEEPSEEK_* in .env as fallback |
ImageModeration/ | Yandex Vision API |
Uploader/ | Storage abstraction (Local, Cloudinary, Yandex Cloud Object Storage); driver selected in admin panel |
Subscription/ | Subscription lifecycle |
Comment/ | Comment domain logic |
Post/ | Post block and quiz processing |
Repositories
Repositories (app/Repositories/) isolate data access:
app/Repositories/
├── Post/
│ ├── Interfaces/PostRepositoryInterface.php
│ └── PostRepository.php
├── Config/
│ ├── Interfaces/GetMailConfigRepositoryInterface.php
│ └── GetMailConfigRepository.php
└── …Interface → implementation bindings are registered in RepositoryServiceProvider.
Config repositories are the single source of truth for config table settings. Values are cached under config.{key}.
Data Transfer Objects (DTOs)
DTOs (app/DTO/) are typed objects for passing data between layers instead of untyped arrays:
// Instead of:
$data = ['title' => '...', 'blocks' => [...], 'category_id' => 1];
// Use:
$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,
);Contracts & Traits
Interfaces (app/Contracts/)
| Interface | Capability |
|---|---|
BookmarkableInterface | Bookmarking |
ReactionableInterface | Reactions |
ReportableInterface | Reporting |
BannableInterface | Banning |
IgnorableInterface | Ignore lists |
FollowableInterface | Following |
HasMedia | Media attachments |
NotifiableInterface | Notifications |
Uploader | File storage |
Traits (app/Concerns/)
Implement interfaces and are mixed into models:
Bannable, Followable, HasBookmarks, HasFollowers, HasIgnores, Ignorable, Reportable, Bookmarkable, Reactionable
Feed Pipeline
The feed system (app/Services/Feed/Feed.php) uses Laravel Pipeline:
Feed
→ Pipeline
→ MyFeed (personal)
→ NewFeed (new)
→ PopularFeed (popular)
→ EditorialFeed (editorial)
→ OrderFeed (ordering)
→ WithoutBlackList (ignored users, keywords)
→ cursorPaginate → post collectionSwappable Drivers (Driver Pattern)
Logic from AppServiceProvider has been split into dedicated service providers: MailServiceProvider, MediaServiceProvider, AiServiceProvider, SlowQueryServiceProvider, ImageModerationServiceProvider. Each provider resolves its own interface at runtime:
// AI service — DeepSeek or null driver
$this->app->bind(AiServiceInterface::class, fn() => ...);
// Image moderation — Yandex Vision or null
$this->app->bind(ImageModerationServiceInterface::class, fn() => ...);
// File storage — Local / Cloudinary / Yandex Cloud
// Driver and credentials are read from DB settings (Admin Panel → Media)
// config/uploaders.php — driver registry (maps driver name to implementation class)
$uploader = UploaderFactory::make($driver); // $driver resolved via config/uploaders.phpAdding a new provider doesn't require changes at call sites. For storage, create the class and add an entry to config/uploaders.php.
Payment Providers
PaymentProviderManager and PaymentProviderFactory in app/Services/Payments/ implement the Driver Pattern for payment providers. Supported drivers from PaymentProviderDriver: YooKassa and T-Bank. Adding a new provider = implement PaymentProviderInterface + register in the factory.
Routes
| File | Purpose |
|---|---|
routes/web.php | Public and authenticated routes |
routes/admin.php | Admin panel routes (prefix: admin-panel) |
routes/channels.php | WebSocket broadcasting channels |
routes/console.php | Artisan Scheduler |
Laravel Sanctum is installed, but there is no dedicated routes/api.php file — external API routes are not separated yet.
Key Models
| Model | Domain |
|---|---|
User | Accounts, roles, settings; communities are User with type = community (UserType::Community) |
Post | Posts with JSON blocks, versions, polls; category_id → community (User) |
Comment | Comments with threading, soft delete |
Tag | Tags (polymorphic via tagables) |
SubscriptionPrice | Subscription plans |
SubscriptionFeature | Subscription feature flags |
UserSubscription | Active subscriptions |
SubscriptionPayment | Payment records |
Ban, ContentBan | User blocks |
Complaint, ComplaintReason | Reports |
Chat, Message | Private messaging |
Notification | User notifications |
Config | DB-stored settings |
Media | Uploaded files |
Advertisement | Ad placements |
Database Structure
87+ migrations. Key characteristics:
- Soft delete on
users,comments,posts - Denormalized counters (
post_counts,post_complaint_counts) for performance - Polymorphic relations for tags, reactions, bookmarks, reports
Infrastructure
docker-compose.yml — development
docker-compose.prod.yml — production
Dockerfile — development image (PHP 8.4 + Node)
Dockerfile.prod — production image
docker/nginx/ — nginx configs
supervisord.conf — Horizon + Reverb