Files
XC_VM/.github/instructions/architecture-rules.instructions.md
T
Divarion_D fda7f41258 refactor(ministra): move Stalker portal from module into core (src/Ministra)
Ministra stops being a module — the whole Stalker portal (portal.php,
MinistraBootstrap, PortalHandler/PortalHelpers and the STB front-end) now
lives in src/Ministra/ under the XcVm\Ministra namespace, served at
/home/xc_vm/Ministra via the nginx alias.

- src/ministra/* and Modules/ministra_85a7d/{PortalHandler,PortalHelpers}
  → src/Ministra/; MinistraModule.php + module.json removed. Ministra was
  the only committed module, so src/Modules/ keeps a .gitkeep.
- portal.php resolves PortalHandler as a sibling and derives MAIN_HOME from
  its new location (glob crutch gone).
- nginx alias + AuthRepository $rAlias switched to /home/xc_vm/Ministra
  (PascalCase); ministra entry dropped from bundled_modules.php.
- Makefile: Modules/ removed from LB_DIRS — all modules are MAIN-only, so
  the ~50 MB of portal assets no longer ship to LB nodes.
- ArchitectureTest: zero committed modules is now a valid state.
- PHPStan: analyse src/Ministra, exclude the procedural portal.php entry,
  repath the ministra baseline entries.
- Docs (architecture, ministra-browser-emulation, extraction plan) updated
  to the new layout; the "extract to a separate repo" plan is cancelled.

Verified: php -l, make gates, make phpstan (No errors), full unit suite
(432 tests). On-server smoke: handshake + get_profile work end-to-end with
a registered MAC after deploy.
2026-08-11 21:41:11 +03:00

3.8 KiB

description
description
Use when creating or modifying services, repositories, controllers, modules, or any domain/core code. Enforces XC_VM architectural rules — see docs/en/development/architecture.md.

Architecture Rules — XC_VM

Structured PHP monolith with a modular extension layer — intentionally no DDD / Hexagonal / Clean Architecture. Split by context with minimal abstractions. Authoritative reference: docs/en/development/architecture.md.

Source tree (PSR-4, XcVm\ → src/)

Path Role
src/Core/ Infrastructure primitives: DI container, events, HTTP router, config, auth, logging
src/Domain/ Business contexts: Stream, VOD, Line, User, Server, Security, … (Controller → Service → Repository)
src/Infrastructure/ Cross-cutting adapters (Database, TMDb client, …)
src/Streaming/ Streaming runtime — used by both MAIN and LB builds
src/Public/ Front controller, router, controllers, views, assets
src/Cli/ Console commands and cron entry points
src/Modules/ Optional extension layer ({name}_{hash5}/), loaded by ModuleLoader
src/Ministra/ Stalker Portal — in core; served at /home/xc_vm/Ministra

Layer pattern

Every domain context follows Controller → Service → Repository → Database:

Public/Controllers/Admin/StreamController.php  → presentation
Domain/Stream/StreamService.php                → business logic
Domain/Stream/StreamRepository.php             → data access
Infrastructure/Database/… + Core/Database/…    → infrastructure

Dependency direction (inward only)

Layer May depend on MUST NOT depend on
Public/ Domain/ (Service + Repository), Core/ Streaming/, Modules/ directly
Domain/ Core/, Infrastructure/ Public/, Modules/
Streaming/ Core/ (subset), Domain/ (read-only) Public/, Modules/
Core/ Only other Core/ subdirectories Everything else
Modules/ Domain/, Core/ Other modules, Public/, Streaming/

Dependency injection

  • ServiceContainer (PSR-11) is used ONLY at the composition root (bootstrap paths / module boot()).
  • After bootstrap, collaborators are passed by constructor; the shared DB connection comes via the DatabaseAware trait + self::db().
  • No service calls $container->get() inside its own methods (no Service Locator).

Module boundaries

  • A module is an isolated directory src/Modules/{name}_{hash5}/ ({Pascal}Module extends BaseModule, namespace XcVm\Module\{Pascal}).
  • Modules own their DB schema via file migrations (migrations/<semver>.sql + database.sql master + database_drop.sql teardown, run by ModuleMigrator). Do NOT add module tables to core src/bin/install/database.sql.
  • Core must never touch module-owned tables directly (a module can be uninstalled, dropping them). Instead core dispatches an event (e.g. StreamsDeletedEvent) and the owning module subscribes via #[ListensTo(Event::class)] to clean up.
  • Removing/disabling a module (via src/config/modules.php) must NOT break the system — graceful degradation.

Multi-build awareness

  • MAIN build: full panel (admin + streaming + MySQL/MariaDB).
  • LoadBalancer build: streaming subset only — admin/reseller/player and privileged CLI are stripped (make lb). Modules are MAIN-only. Code in Domain/ / Streaming/ reached by the LB must not pull admin-only dependencies.

Decision filters

Before an architectural decision, apply:

  1. Can a contributor understand it in 5 minutes? If no → simplify.
  2. Does it break the streaming hot path? If yes → reject.
  3. Can it be isolated as a module? If no → justify why.

If a change improves "code beauty" but raises the entry barrier → reject it.