Container Scopes
Overview
Container scopes let you create isolated sub-containers that maintain their own bindings and instances while still falling back to the parent container. This is particularly useful when you need to adjust or override dependencies in a specific context (such as a request, session, or test scenario) without affecting the global container state.
How Scopes Work
When you create a scope, the container instantiates a new sub-container that:
-
Inherits Bindings:
It automatically inherits all bindings and instances from its parent container. -
Local Overrides:
You can register new bindings or override existing ones specific to that scope. -
Fallback Resolution:
If a service is not found in the sub-container, resolution falls back to the parent container.
This behavior ensures that services can be tailored for a specific context without impacting the global configuration.
The scope()
Method
The scope()
method is used to create or retrieve a lazy-scoped sub-container. Its signature is:
scope(
string $scopeName,
callable $callback
): mixed
Parameter | Type | Required | Description |
---|---|---|---|
$scopeName | string | Yes | A unique name to identify the sub-container (scope). |
$callback | callable(Container): mixed | Yes | A callback that receives the sub-container instance to configure or retrieve services within this scope. |
Extended Example: Using Scopes with Instance Creation
Below is an extended example that demonstrates how to use scopes, override a global binding within the scope, and utilize the created instance.
// Create the main container.
$container = new Container();
// Global binding: LoggerInterface resolves to DefaultLogger.
$container->bind(LoggerInterface::class, DefaultLogger::class);
// Resolve the global logger and use it.
$globalLogger = $container->get(LoggerInterface::class);
$globalLogger->log("This is from the global DefaultLogger.");
// Create a sub-container (scope) for a request.
$requestLogger = $container->scope('request', function (Container $scope) {
// Override the LoggerInterface binding for this scope.
$scope->bind(LoggerInterface::class, RequestLogger::class);
// Resolve and return the instance within the scope.
return $scope->get(LoggerInterface::class);
});
// Use the instance created within the request scope.
$requestLogger->log("This is from the RequestLogger within the 'request' scope.");
// Demonstrate that resolving LoggerInterface from the parent still returns the global logger.
$anotherGlobalLogger = $container->get(LoggerInterface::class);
$anotherGlobalLogger->log("Back to the global DefaultLogger.");
In this example:
- The container is set up with a global binding where
LoggerInterface
maps toDefaultLogger
. - A new scope named
"request"
is created via thescope()
method. Inside the callback, the binding forLoggerInterface
is overridden to useRequestLogger
. - The instance resolved from the scoped container (
$requestLogger
) reflects the local override. - Resolving
LoggerInterface
from the parent container still returns the global instance, confirming that the scope did not affect the global state.
Underlying Implementation Details
-
Scoped Storage:
Each scope is stored in an internal associative array using its unique scope name. -
Method Overrides:
The sub-container overrides key methods likehas()
,get()
, andmake()
to first check its own bindings and instances, then fallback to the parent container if necessary. -
Lazy Initialization:
If a scope with the given name already exists, it is reused rather than recreated, ensuring efficient resource usage.
Benefits of Using Scopes
-
Isolation:
Tailor service definitions for specific contexts without modifying global bindings. -
Override Capability:
Temporarily override or extend service bindings for testing, requests, or other scenarios. -
Resource Management:
Create temporary sub-containers that can be disposed of after use, reducing potential memory overhead in unit tests or per-request handling.
Summary
Container scopes in the DomainFlow Container provide a flexible mechanism to isolate and override service bindings for specific contexts. By creating sub-containers that inherit global bindings yet allow local modifications, you can fine-tune dependency resolution for different parts of your application without compromising the integrity of the global configuration.