Dependency Injection
The documentation is constantly evolving and updated alongside the project's progress. Since it is developed in parallel with the code, some sections may temporarily refer to earlier versions of the system-these are gradually adapted to the current state of the application. If you notice any outdated information, please report it or open an Issue / Pull Request.
Documentation
# Dependency Injection
# Overview
DbM Framework uses a lightweight Dependency Injection container (DependencyContainer) for managing application services. It combines explicit service registration with constructor autowiring for eligible concrete classes.
The container allows you to:
- Register services manually
- Share instances using
singleton - Resolve dependencies automatically (autowiring)
- Organize modules via service providers
Service configuration file:
bootstrap/services.php
# Service Registration
Services are registered inside a closure:
return function (DependencyContainer $container): void {
// services here
};
# singleton() vs set()
# singleton (recommended)
Creates and caches one instance per container lifecycle. In a typical PHP request, the container itself is request-scoped:
$container->singleton(Logger::class, fn() => new Logger());
or shorter:
$container->singleton(RouteCollection::class);
# set
Calls the registered factory each time get() resolves this identifier (or applies custom factory logic):
$container->set(DatabaseInterface::class, fn() =>
isConfigDatabase() ? DatabaseFactory::createDatabase() : null
);
# Example Configuration
$container->singleton(Request::class);
$container->singleton(SessionManager::class);
$container->singleton(CookieManager::class);
$container->singleton(
FlashBag::class,
fn($c) => new FlashBag($c->get(SessionManager::class))
);
# Dependency Resolution
# Constructor Injection
Dependencies are automatically injected into constructors:
class IndexController
{
public function __construct(
private IndexService $service,
private FlashBag $flash
) {}
}
When all constructor dependencies are eligible concrete classes or registered services, the container can build the object without a manual $container->get() for each dependency. Interfaces and scalar values normally need explicit bindings.
# Controller Example
class IndexController extends BaseController
{
public function __construct(
private readonly IndexService $service,
private readonly FlashBag $flash
) {}
public function index(): ResponseInterface
{
$this->flash->set('Application is ready');
return $this->render('index/start.phtml', [
'meta' => $this->service->getMetaIndex(),
]);
}
}
The container is intentionally neither a compiled container nor an automatic directory scanner. When get() reaches an unbound, eligible concrete class, it uses ReflectionClass to inspect that class constructor and recursively resolve non-builtin named parameter types. This keeps discovery local to a requested service while retaining convenient constructor injection.
Interfaces and scalar parameters are not guessed: bind interfaces to factories or instances, and pass configuration values explicitly. Default-valued constructor parameters can use their defaults. If a parameter cannot be resolved, the container reports the class and parameter.
# Autowiring
The container automatically resolves:
- Constructor dependencies
- Controller action arguments
# Example
public function show(ServerRequestInterface $request): ResponseInterface
If Request is registered → it will be injected automatically.
# Dynamic Resolution
You can still resolve services manually:
$container->get(SomeService::class);
If the class:
- exists
- has resolvable dependencies
it will be created automatically.
# Routing Integration
The DI container is deeply integrated with routing:
ControllerResolvercreates controllersActionArgumentResolverinjects method argumentsRouterorchestrates full request lifecycle
# Modules (Service Providers)
Modules can register their own services:
$module->register($container);
ModuleBootstrapper calls enabled modules register methods during startup, allowing modular service registration and feature isolation.
# Template Integration
Services can be exposed to templates:
$view->setGlobal(
'headerNavigation',
fn() => $this->container
->get(NavigationUtility::class)
->getHeader($request->getUri()->getPath())
);
# Usage in Template
{% $headerNavigation = $this->headerNavigation(); %}
{% if (is_array($headerNavigation)): %}
# Core Service Groups
# HTTP
RequestSessionManagerCookieManager
# Routing
RouterRouteCollectionRouteMatcherControllerResolverActionArgumentResolver
# View
TemplateEngineFlashBag
# Security
AccessGuardCsrfTokenManager
# Localization
LanguageServiceTranslationTranslationLoader
# Infrastructure
LoggerFilesystemDbm\Infrastructure\Mail\Contract\MailerInterface(registerMailServiceProviderto use the built-in native or SMTP transport; DBM Platform can register its PHPMailer adapter)
# Best Practices
- Use
singleton()for most services - Use
set()for dynamic or optional services - Prefer constructor injection over manual
get() - Keep services stateless when possible
- Explicitly register important services (avoid overusing auto-resolution)
# When to Use set() Instead of singleton()
Use set() when:
- Service may return
null - Service depends on runtime conditions
- You need a new instance each time
# Summary
DbM DI container provides:
- Lightweight resolution: no directory scanning or application-wide class discovery; ReflectionClass inspects only a requested concrete class when constructor autowiring is needed
- Full control over service creation
- Automatic dependency resolution
- Clean architecture with modules
DI DbM is: not Laravel, not Symfony, just a lightweight, controlled container.
It combines simplicity with flexibility, making it suitable for both small apps and complex modular systems.