-
Notifications
You must be signed in to change notification settings - Fork 145
Description
Dependency injection enables classes to obtain their required dependencies from outside, rather than creating or accessing them directly (e.g., via singletons or static calls).
Object creation and linking are managed centrally, while individual classes focus exclusively on their own tasks.
Goal
The goal of introducing dependency injection is to gradually reduce dependence on singletons and global/static states, provide a clear and extensible service architecture, and create a solid foundation for unit testing and long-term maintainability.
Advantages
- First step toward proper unit testing
By eliminating hard dependencies (e.g., singletons, static access), services can be easily simulated or replaced, enabling isolated unit testing. - Clear and explicit structure
Dependencies become visible through constructors/members instead of remaining hidden behind static calls. - Loose coupling
The core code depends on interfaces, not concrete implementations, which improves maintainability and safety during refactoring. - Better extensibility for plugins
Plugins can provide their own service implementations without modifying the core code, as long as the extension points are clearly defined.
Service registration
New services (provided by both the core and plugins) should be registered during the bootstrap phase(Bootstrap Scripts).
Required changes
To introduce Dependency Injection the following steps are required:
-
Introduce a ServiceRegister API
The ServiceRegister is responsible for:- Management of the services
- Handling overwrites of an already registered service
- Multiple registration of a service, e.g., different payment providers
- Management of the containers
- Creation of new instances of the service, if necessary
- Management of the services
-
Extend the bootstrap script
The bootstrap mechanism must be extended so that a centralServiceRegister(or equivalent) is passed to bootstrap scripts.
Core and plugin bootstrap scripts must register their services explicitly via this registry. -
Deprecate existing singletons
All existing singleton-based services should be marked as deprecated and exposed as services via the DI container.
The direct use of singletons outside of containers is therefore deprecated and must be revised. -
Gradual removal of singletons
After a transition period, deprecated singletons should be removed entirely and replaced by proper service injection.
Code example
Use php attributes to mark a service that can then be injected.
interface IFooService {
public function fooBar(): int;
}
#[Service]
final class MyFooService implements IFooService
{
public function fooBar(): int {
// do something
}
// ...
}Bootstrap-Script
return static function (ServiceRegister $register): void {
$register->register(MyFooService::class, IFooService::class);
// …
}Usage
#[Container]
class MyContainer {
#[Inject]
private IFooService $service;
public function doSomething(): void {
$i = $service->fooBar();
// …
}
}PHP attributes should only be used to mark that this is a service and, if necessary, to offer configuration options. Not to find them using reflection scanning.
Possible libraries
The following libraries could be used to implement this feature:
-
PHP-DI
PSR-11-compatible container with autowiring, attribute support, and container compilation.
https://php-di.org/ -
Symfony DependencyInjection
Mature DI container with powerful configuration and compiler passes
https://symfony.com/components/DependencyInjection