Skip to content

Architecture ​

Canonical source: ARCHITECTURE.md at the repo root is the single source of truth. This page is a short, scannable mirror; when they differ, ARCHITECTURE.md wins.

Roollith is a minimal, synchronous PHP micro-framework with an MVC shape. One entrypoint boots shared services, routes one HTTP request to one controller action, optionally touches the database, renders a view, then cleans up.

The framework is thin glue (app/, config/, views/, index.php, constant.php) over eight standalone roolith/* Composer libraries, plus Carbon for time and Whoops for dev errors. There is no app-level container (only per-service factories), no background worker, and no built-in ORM relationship manager.

Full detail lives in ARCHITECTURE.md. For the file layout, see Getting Started.

The mental model in 30 seconds ​

  • One request, one path: index.php -> System::run() -> Router -> one Controller@action -> View or Response -> cleanup.
  • Convention over configuration: routes in app/Http/routes.php, controllers in app/Controllers, models in app/Models, views in views/.
  • Pay for what you use: the DB connection is skipped when database config is null. Cache and events only run when your code calls them.

Design principles ​

  • Minimal core, composable packages: each roolith/* library works outside the framework.
  • Fixed locations for routes, controllers, models, views, config, and constants.
  • Synchronous request scope: per-request state lives in superglobals and session; System::complete() cleans up (disconnect + removeTemp).
  • Explicit extension points: routes, middleware, base Controller, base Model, view partials, language files, generator templates, APP_ENABLE_CMS.
  • Observable early: Vite HMR plus full-page reload on PHP change, Whoops pages in development only.

Where things run ​

mermaid
flowchart LR
    Browser[Browser] -->|HTTP :8080 dev| PHPApp[PHP App - Apache + index.php]
    Browser -->|HMR| Vite[Vite :5173]
    Vite -->|proxy pages| PHPApp
    PHPApp -->|PDO, optional| MySQL[(MySQL :3306)]
    PHPApp -->|built files| Assets[assets/build]
    Browser -->|loads| Assets
  • Production: browser talks directly to PHP. Vite and phpMyAdmin are dev aids only.
  • MySQL is the only durable dependency, and it is optional (database: null runs DB-less).
  • See Docker and Frontend Workflow.

How code is layered ​

mermaid
flowchart TB
    B[Browser] --> Entry[index.php + System]
    Entry --> Router[Routing + Middleware]
    Router --> Ctrl[Controllers]
    Ctrl --> View[Views]
    Ctrl --> Model[Models + DB]
    Ctrl --> State[Storage + Session + Settings]
    Entry --> Platform[Config + Cache + Events + Utils + Errors]
    Platform -.-> Ctrl
LayerOwnsExamples
DeliveryEntry, URL policy, routingindex.php, System, PreProcessor, RouterFactory
ApplicationRequest orchestrationControllers, Request, Validator, Storage, Language
PresentationHTML outputviews/, template engine, source/ + assets/build
Domain and dataPersistenceBase Model, app models, DatabaseFactory, MigrationFactory + roolith/migration
PlatformShared, dependency-free servicesConfig, cache, events, utils, errors, generator

Dependency rule

Delivery depends on application, application depends on domain/data and presentation, everything depends on platform. Vendor libraries never import from App.

How a request flows ​

System::run() never throws. It wraps bootstrap -> processRequest -> complete, emits redirects, and routes failures to ErrorHandler.

mermaid
sequenceDiagram
    participant Browser
    participant Sys as System
    participant R as Router + Middleware
    participant C as Controller
    participant D as Model + DB / View
    Browser->>Sys: HTTP request
    Sys->>Sys: bootstrap - env, config, session, headers, DB
    Sys->>R: load routes + match method + path
    alt no match
        R-->>Browser: views/404.php (405 + Allow header if wrong method)
    else match
        R->>C: dispatch one action
        C->>D: query models if needed, return string or Response
        D-->>Browser: HTML or JSON
    end
    Sys->>Sys: complete - disconnect DB + clear temp (always, even on redirect/exit)
  1. index.php calls System::run(). System loads .env, constants, helpers, trace id plus logger, and the error mode (Whoops in development only).
  2. bootstrap() validates config (ConfigValidator), starts the session (Session::start()), sends security headers, applies forceNonWww/forceWww (301, allowlisted, RedirectException on match), and connects to the DB only if configured. See Security and Configuration.
  3. processRequest() resets RouterFactory, loads routes.php, validates handlers log-only (php roolith route:list is the lint tool), matches method plus path, and runs the middleware chain (NextMiddlewareInterface::process(request, next)). See Routing and Middleware.
  4. One controller action runs: reads raw input via Request, validates via Validator, queries via models, returns string|App\Core\Response (HTML via view():string, JSON via $this->json()). See Controllers, Request, Response.
  5. RouterResponse emits the result. complete() clears one-shot temp data and disconnects the DB (idempotent, also runs on redirect and shutdown). Failures go to ErrorHandler with trace id (rethrow in dev, generic 500 in prod). See Error Handling and Logging.

Subsystems at a glance ​

Details live in dedicated pages. This table is the map, not the manual.

You want toReach forRead more
Boot, config, envSystem, Env, ConfigValidator, config/config.phpConfiguration, Using Dot ENV
URLs and access controlroutes.php, RouterFactory, app/Middlewares/*Routing, Middleware, CLI
Page behaviorapp/Controllers/* + base Controller (view(), json())Controllers, Response
Page markupviews/*.php, inject, escape, viteJs/viteCssViews, Custom View Engine
Input and filesRequest (raw), Sanitize::param()/email() (narrow), File + FSRequest, Validation, File Upload
Validation rulesValidator + Rules per field (success/fails/errors)Validation
Reads and writesBase Model (all/orm/raw), $fillable, $casts, LazyLoad::with()Models, Database, Extending a Model
Schema and seedsMigrationFactory + roolith/migration (migrate*, seed*), DatabaseFactory::transaction()Migration, Seeder, CLI
Cookies and sessionsStorage, Session (start/regenerate), Settings (locale)Storage, Session
Abuse controlSessionRateLimiter (hit/tooManyAttempts/clear)Middleware, Security
Text and localeLanguage, trans() (__() is a BC alias), lang/{locale}/Localization
HelpersArr, Collection, Str, FS, Support/* (Url, Redirect, Html, Debug, IdGenerator)Array Helpers, Collections, Support Helpers
DatesCarbon via getCurrentDateTime() etc.Date Helpers
Cache and eventsroolith/cache, roolith/event, on-demand onlyCache, Events
Scaffoldingphp roolith generate from app/Core/generator-templatesGenerator, CLI
Frontend assetssource/ -> assets/build/ (hashed, manifest), HMR via viteDevServerFrontend Workflow
DeploysDockerfile, docker-compose.yml, docker-compose.prod.yml, .htaccessDocker, CMS Installer

Notes worth knowing once:

  • Request::input() returns raw values. Validate by type, escape at render with escape(). Sanitize::any()/string()/items() are legacy; only param() (slugs) and email() are current.
  • trans() is canonical; __() is a BC alias. Arr is canonical; _ is a BC alias for PHP 8.4.
  • There are no declarative relations. LazyLoad::with(model, foreignKey, localKey) does one batched manual eager load.
  • Cache and events are shipped but unwired: nothing calls them unless your code does.

Data: what lives where ​

Controller -> Model::orm() -> Database -> MySQL. One table per model class, single-column primary key defaulting to id.

KindWhereExamples
DurableMySQL over PDO via roolith/databaseApp tables
EphemeralSession, cookies, in-memory per requestFlash messages, rate-limit counters, locale reference
FilesLocal filesystem via FS::uploadUploads

No cache or queue is required for the default flow.

Cross-cutting concerns ​

  • Errors: Whoops in dev, silent 500 plus trace id in prod. See Error Handling.
  • Security: raw input plus render-time escaping, upload allowlist, rate limiting, canonical hosts, baseline headers. See Security.
  • Observability: PSR-3 file logger with per-request trace id on bootstrap, routing, 404, and failures. See Logging.
  • Conventions as contracts: one shared router, view engine, and DB handle per request; php roolith route:list lints handlers. See Testing.

Extension map ​

To changeTouchLeave alone
URLs and access controlapp/Http/routes.php, app/Middlewares/*RouterFactory, vendor router
Page behaviorapp/Controllers/* extending base ControllerSystem, index.php
Page markupviews/*.php, views/partials/*template engine package
Domain reads and writesapp/Models/* extending base Model, LazyLoadDatabaseFactory unless connection policy changes
Input rulesValidator plus Rules declarations in controllersSanitize primitives
Localeslang/{locale}/message.phpLanguage, Settings
Frontend assetssource/js, source/scss, vite.config.mjsview helpers viteJs, viteCss
Scaffoldsapp/Core/generator-templates/*.txtroolith script wiring
CMS modeAPP_ENABLE_CMS=1 plus CMS release assetcore lifecycle
Persistence enginecustom ORM or view engine docscontroller call sites if the Model facade is preserved

Constraints and tradeoffs ​

  • Single-request PHP execution with no async path; long work must move outside the request.
  • Singleton factories simplify sharing but hide dependencies; reset or replace them in tests.
  • Session-local rate limiting and temp data do not scale horizontally without shared session storage.
  • Manual eager loading avoids ORM magic at the cost of explicit with() calls per association.
  • Static facades (Request, Storage) optimize for brevity over injection; app/Core/Interfaces is the seam for injection later.

Where to find things ​

PathRole
index.phpThin process entry (System::run())
constant.php, config/config.phpConfiguration
app/HttpDelivery wiring (routes)
app/Controllers, viewsApplication and presentation
app/Models, app/Core/DatabaseFactory.php, app/DatabaseData access, migrations, seeders
app/CoreHTTP, validation, state, i18n, lifecycle
app/Utils, app/SupportDependency-free helpers
app/MiddlewaresEdge policy
source, assets/buildFrontend authoring and output
langLocalization data
vendor/roolith/*Replaceable platform
roolith, app/Core/generator-templatesScaffolding
Dockerfile, docker-compose*.yml, .htaccessOperations

Released under the MIT License.