Docs LATEST

Architecture Overview

Core services, events, and boot flow.

Architecture

VelvetCMS is built around a lightweight, lazy-loading architecture. Everything wires together through explicit bindings rather than hidden magic. This page covers the big picture-how requests flow through the system and how the pieces connect.

Application Lifecycle #

Every request goes through these stages:

  1. Bootstrap - bootstrap/app.php creates the Application container
  2. Service Registration - CoreServiceProvider registers all core services
  3. Module Loading - Modules are discovered and their providers are registered
  4. Boot - Service providers receive their boot() call
  5. Handle - HTTP requests route through middleware and controllers; CLI requests dispatch commands
Request → Container → Router → Middleware → Controller → Response

The container acts as the central hub. It holds services, resolves dependencies, and coordinates everything else.

Service Container #

The container is a dependency injection system. You register services with it, then resolve them when needed-either by name or interface.

Binding Services #

// Singleton - same instance every time
$app->singleton(CacheDriver::class, FileCache::class);

// Factory - new instance every time
$app->bind(Logger::class, fn($app) => new FileLogger(
    logPath: $app->config('logging.path'),
    level: $app->config('logging.level'),
    daily: $app->config('logging.daily', false),
));

// Instance - pre-built object
$app->instance('custom.service', $myServiceInstance);

Resolving Services #

// By class name
$cache = $app->make(CacheDriver::class);

// By alias
$cache = $app->make('cache');

// Via helper
$cache = app('cache');

Autowiring #

If you request a class that has no explicit binding, the container tries to instantiate it automatically. Constructor dependencies are resolved recursively:

class MyController
{
    public function __construct(
        private readonly CacheDriver $cache,
        private readonly PageService $pages
    ) {}
}

// Container resolves both dependencies automatically
$controller = $app->make(MyController::class);

Autowiring is intentionally limited to concrete classes. If a class depends on an interface, that interface must be bound in the container first.

Events #

The event dispatcher lets you hook into key lifecycle points without modifying core code. Events are notifications-they inform you that something happened.

$events = app('events');

// Listen for an event
$events->listen('page.saved', function (array $page) {
    // Invalidate cache, trigger webhooks, log activity...
});

// Fire an event
$events->dispatch('page.saved', $pageData);

Core emits events like app.booting, app.booted, router.matched, and page.saved. See Standard Events for the full list.

Modules #

Modules extend the core with new routes, commands, views, and services. Each module has a manifest (module.json) and optionally a service provider.

modules/
└── Blog/
    ├── module.json
    ├── src/
    │   └── BlogServiceProvider.php
    ├── routes/
    └── views/

Modules are loaded from paths defined in config/modules.php. The module manager discovers them, registers their providers, then boots them in dependency order.

See the Modules page for manifest format and discovery details.

What's Next #