Architecture Overview
Core services, events, and boot flow.
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:
- Bootstrap -
bootstrap/app.phpcreates theApplicationcontainer - Service Registration -
CoreServiceProviderregisters all core services - Module Loading - Modules are discovered and their providers are registered
- Boot - Service providers receive their
boot()call - 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 #
- Lifecycle - step-by-step request flow
- Directory Structure - where things live
- Modules - building and loading modules