Omega MVC — Service Provider Manual
Service providers are the composition root of an Omega MVC application. A provider encapsulates the two phases of bootstrapping — register (bind services into the container) and boot (use already-bound services: register routes, attach event listeners, configure integrations). Providers ship with vendor packages and are auto-discovered via Composer extra.omega-mvc.
The provider contract lives in Omega\Container (AbstractServiceProvider + AppServiceProviderTrait); the registration/boot machinery lives in Omega\Application.
The provider contract
namespace Omega\Container;
use Omega\Application\ApplicationInterface;
abstract class AbstractServiceProvider
{
use AppServiceProviderTrait;
protected array $register = []; // declarative class-string list
public function __construct(protected ApplicationInterface $app) {}
public function boot(): void {}
public function register(): void {}
}- The constructor receives the application (which is the container) —
$this->appis how you bind services. - Override
register()to declare bindings; it is called before any boot. - Override
boot()for post-registration work; it runs after every provider has been registered. - The
$registerproperty is a declarative list of class-strings that subclasses may fill; there is no automatic processing loop in the base class — use it as a convention (e.g. iterate it insideregister()).
Writing a provider
namespace App\Providers;
use Omega\Application\ApplicationInterface;
use Omega\Container\AbstractServiceProvider;
final class AppServiceProvider extends AbstractServiceProvider
{
public function register(): void
{
$this->app->bind(StatsInterface::class, StatsCollector::class, shared: true);
$this->app->bind('app.locale', fn () => config('app.locale', 'en'));
}
public function boot(): void
{
$templator = $this->app->get(Omega\View\Templator::class);
$templator->share('year', (new Omega\Time\Now())->format('Y'));
$dispatcher = $this->app->get(DispatcherInterface::class);
$dispatcher->addListener('user.registered', [...]);
// Routes can be registered here via Omega\Router\Router
}
}Register it with the application:
$app->register(App\Providers\AppServiceProvider::class);Registering: semantics
Application::register(string $provider): AbstractServiceProvider
- Already-loaded provider? Returns a fresh instance without re-registering (dedup guard).
- Otherwise it constructs
new $provider($app), calls$provider->register(), and marks it loaded. - Late provider: if the app is already booted (
isBooted === true), the provider is booted immediately and synchronously.
The boot phase
Application::bootProvider() runs:
bootingcallbacks (bootingCallback()),- each not-yet-booted core provider's
boot()method (order below), bootedcallbacks (bootedCallback()— also fires immediately for callbacks registered after booting),- marks the application as booted (
$app->isBooted).
Bootstrap flow in the framework
use Omega\Application\Bootstrapper\RegisterProviders;
use Omega\Application\Bootstrapper\BootProviders;
$app->bootstrapWith([
RegisterProviders::class, // 1. register phase for ALL providers
BootProviders::class, // 2. boot phase via bootProvider()
]);RegisterProviders
Unions three provider sources and registers each (after filtering to real AbstractServiceProvider subclasses and deduplicating):
$app->getCoreProviders()— the built-in provider stack;- config providers —
config('providers', []), only if theconfigbinding resolves to aConfigRepository; - package providers —
ApplicationManifest::providers(), i.e. what each installed Composer package declares underextra.omega-mvc.
BootProviders
Simply delegates: $app->bootProvider().
Core providers (boot order)
The concrete Application boots these providers in order:
1. Omega\Exceptions\WhoopsServiceProvider
2. Omega\Logging\LoggingServiceProvider
3. Omega\Event\EventServiceProvider
4. Omega\Cron\CronServiceProvider
5. Omega\Security\HashServiceProvider
6. Omega\Router\RouteServiceProvider
7. Omega\Session\SessionServiceProvider
8. Omega\Database\DatabaseServiceProvider
9. Omega\View\ViewServiceProvider
10. Omega\Cache\CacheServiceProvider
11. Omega\RateLimiter\RateLimiterServiceProvider
12. Omega\Redis\RedisServiceProvider
13. Omega\Http\MacroServiceProvider
14. Omega\Csrf\CsrfServiceProviderVendor-package providers — ApplicationManifest
A vendor package advertises one or more providers via Composer metadata:
{
"name": "acme/widgets",
"extra": {
"omega-mvc": {
"providers": ["Acme\\Widgets\\WidgetServiceProvider"]
}
}
}ApplicationManifest reads <base>/vendor/composer/installed.json, keeps every package with extra['omega-mvc'], writes a var_export cache at <bootstrap.cache>/packages.php, and exposes providers(): array (flattened, non-empty class-string values). The disk cache short-circuits installed.json; delete packages.php to rebuild. Bind to get it directly:
use Omega\Application\ApplicationManifest;
$manifest = $app->make(ApplicationManifest::class);
$manifest->providers();
$manifest->build(); // force rebuildExporting files and dirs — AppServiceProviderTrait
The trait gives providers the ability to publish package assets (configs, views, migrations) into the application:
use Omega\Container\AbstractServiceProvider;
use function Omega\Application\get_path;
final class WidgetServiceProvider extends AbstractServiceProvider
{
public function register(): void
{
self::export([
__DIR__ . '/../config/widgets.php' => get_path('path.config') . 'widgets.php',
__DIR__ . '/../resources/views' => get_path('path.view') . 'widgets',
]);
}
}
use WidgetServiceProvider as P;
P::importFile($from, $to, $overwrite: true); // bool — copy single file
P::importDir($from, $to, $overwrite: true); // bool — recursive copy
P::getModules(); // array grouped by tag
P::flushModule(); // clear the static registryexport(array $path, string $tag = '')declares source → destination mappings into a static registry ($modules), keyed by an optional tag; the framework'svendor:publishcommand (Omega\Console\Commands\VendorPublishCommand) readsgetModules()and performs the copies viaimportFile/importDir. Import methods returnfalsewhen the source is missing/unreadable and throwException(You do not have permission to overwrite the destination file.) on existing destinations when$overwriteisfalse.
Exceptions to remember
- Container exceptions (registering-time binding errors):
BindingResolutionException,EntryNotFoundException,AliasException,CircularAliasException. - Manifest miss-use:
$app->make(ApplicationManifest::class)requires theApplicationManifest::classfactory binding the app constructor sets. AbstractServiceProvider::__constructrequires theApplicationInterfaceargument; constructing a provider outside the app'sregister()is possible but unusual (you would passapp()).
Notes
- There is no separate
ServiceProviderInterfaceand no deferred/provider- priority mechanism: registration order is the config/core list order, and a provider registered after boot boots immediately. boot()andregister()are empty by default — a provider may implement only one of them.- Providers are resolved as classes (not singletons):
register()returns/uses a per-call instance; the dedup guard lives inApplication::$loadedProviders. - Keep
register()free of runtime dependencies: bind, don't resolve. Useboot()for anything that reads bound services.
Reference
Omega\Container\AbstractServiceProvider.php,Omega\Container\AppServiceProviderTrait.phpOmega\Application\ApplicationInterface::register/bootProvider/bootstrapsOmega\Application\Bootstrapper\RegisterProviders.php,Bootstrapper/BootProviders.phpOmega\Application\ApplicationManifest.php- Tests:
tests/Unit/Container/AbstractServiceProviderTest.php,tests/Unit/Application/ - License: GPL-3.0+