Implements a PSR-11 compliant dependency injection container using a container-interop/service-provider.
Description
This container SHALL resolve services by delegating to the factories and extensions defined in the provided ServiceProviderInterface instance.
Services are lazily instantiated on first request and cached for subsequent retrieval, enforcing singleton-like behavior within the container scope.
The container supports service extension mechanisms by allowing callable extensions to modify or enhance services after construction, based on the service identifier or its concrete class name.
If an optional wrapper container is provided, it SHALL be passed to service factories and extensions, allowing for delegation or decoration of service resolution. If omitted, the container defaults to itself.
Interfaces
Extends the PSR-11 ContainerInterface to provide a consistent, domain-specific container interface for the FastForward ecosystem.
Properties
Cache of resolved services keyed by their identifier or class name.
Final class-extension values indexed by original and same-class resulting object identities.
Active provider entries grouped by Fiber, or by wrapper in the main context.
The container instance used for service resolution and extension application.
Methods
Constructs a new ServiceProviderContainer instance.
Retrieves a service from the container by its identifier.
Determines if the container can return an entry for the given identifier.
Reuses a completed result or executes one extension for the current concrete class.
Runs an ID extension against a reused value without repeating an already completed class-ID extension.
Applies ID-based and concrete-class extensions, retaining each returned value.
Rejects re-entry into this provider's active service while allowing ordered fallback to other providers.
Stores the final value under its requested ID before adding any inferred alias.
Reads the latest completed value and compresses acyclic identity paths.
Normalizes completed replacement values before recording the current class-extension chain.
Updates existing acyclic identities to their latest completed result.
Invokes an available factory and applies the entry's extensions to its value.
Executes an ID callback and identifies whether it also completed a class-ID extension.
Follows recorded identities while retaining a bounded result for cyclic paths.
Checks existing entries using metadata already loaded by the caller.
Infers a concrete-class alias only when it cannot replace another entry or definition.
Reads factory and extension metadata in provider order without executing their callables.
Looks up an object identity without inspecting or executing extension definitions.
Records completed results while leaving different-class replacements eligible for their own extensions.
Resolves an entry once, preserving construction failures and the final cached value.
Restores the caller's path after resolution or releases an empty execution context.
Cache of resolved services keyed by their identifier or class name.
private
array<string, mixed>
$cache
=
[]
Description
This array SHALL store already constructed services to enforce singleton-like behavior within the container scope.
Final class-extension values indexed by original and same-class resulting object identities.
private
WeakMap<object, array{value: mixed}>|null
$classExtensionResults
=
null
Description
Weak keys permit discarded input objects to be collected after replacement. Wrapping values in an array distinguishes a cached null result from an absent entry.
Active provider entries grouped by Fiber, or by wrapper in the main context.
private
static
WeakMap<object, array<int, array{container: self, id: string}>>|null
$resolutionPaths
=
null
Description
Provider identity permits ordered fallback to another source for the same ID. Completed resolutions remove their entries instead of retaining service graphs.
private
ServiceProviderInterface
$serviceProvider
The container instance used for service resolution and extension application.
private
ContainerInterface
$wrapperContainer
Description
This property MAY reference another container for delegation, or default to this container instance.
Constructs a new ServiceProviderContainer instance.
public
__construct(ServiceProviderInterface
$serviceProvider[, ContainerInterface|null
$wrapperContainer = null]) : mixed
Description
This constructor SHALL initialize the container with a service provider and an optional delegating container. If no wrapper container is provided, the container SHALL delegate to itself.
Parameters
$serviceProvider
:
ServiceProviderInterface
Description
the service provider supplying factories and extensions
$wrapperContainer
:
ContainerInterface|null
=
null
Description
An optional container for delegation. Defaults to self.
Retrieves a service from the container by its identifier.
public
get(
string
$id) : mixed
Description
This method SHALL return a cached instance if available, otherwise it resolves the service using the factory provided by the service provider.
Extensions SHALL receive the previous value and may replace it. Without a factory, an ID-based extension receives null. The final value SHALL be cached, including null.
Parameters
$id
:
string
Description
the identifier of the service to retrieve
Description
if factory or extension resolution fails or a dependency cycle exists
Return values
Description
the service instance associated with the identifier
Determines if the container can return an entry for the given identifier.
public
has(
string
$id) : bool
Description
This method MUST return true for cached entries, callable factories, or callable ID-based extensions. It SHALL NOT execute factories or extensions to discover an entry.
Parameters
$id
:
string
Description
identifier of the entry to look for
Return values
Description
true if the entry exists, false otherwise
Reuses a completed result or executes one extension for the current concrete class.
private
applyConcreteClassExtension(
string
$id,
mixed
$service,
array<string, mixed>
$extensions,
array<int, object>
$pendingObjects) : array{mixed, list
Parameters
$id
:
string
Description
the requested service identifier
$service
:
mixed
Description
the result after the ID extension
$extensions
:
array<string, mixed>
Description
the provider's extension definitions
$pendingObjects
:
array<int, object>
Description
class identities being completed in this resolution
Return values
Description
the concrete-class result and any newly completed input identity
Runs an ID extension against a reused value without repeating an already completed class-ID extension.
private
applyIdentifierExtension(
string
$id,
mixed
$service,
callable
$extension) : array{mixed, list
Parameters
$id
:
string
Description
the requested service identifier
$service
:
mixed
Description
the factory result or previously extended object
$extension
:
callable
Description
the callable registered under this identifier
Return values
Description
the ID-extension result and identities completed by a class-ID extension
Applies ID-based and concrete-class extensions, retaining each returned value.
private
applyServiceExtensions(
string
$id,
mixed
$service,
array<string, mixed>
$extensions) : mixed
Description
The ID-based extension SHALL run first. If its result is an object, an extension for that object's concrete class MAY run next. Each object's class-extension result SHALL be reused for later aliases, including replacement objects and null values. ID-based extensions for other identifiers SHALL still run independently. Invalid extensions SHALL be ignored.
Extensions declared void SHALL retain the previous value for compatibility.
Parameters
$id
:
string
Description
the identifier of the resolved service
$service
:
mixed
Description
the previous entry value, or null when no factory exists
$extensions
:
array<string, mixed>
Description
the provider's extension definitions
Return values
Description
the final entry value
Rejects re-entry into this provider's active service while allowing ordered fallback to other providers.
private
assertEntryIsNotResolving(
string
$id,
array<int, array{container: self, id: string}>
$path) : void
Parameters
$id
:
string
Description
the requested service identifier
$path
:
array<int, array{container: self, id: string}>
Description
the current execution context's active entries
Stores the final value under its requested ID before adding any inferred alias.
private
cacheResolvedService(
string
$id,
mixed
$service,
string|null
$alias) : mixed
Parameters
$id
:
string
Description
the requested service identifier
$service
:
mixed
Description
the completed service value, including null
$alias
:
string|null
Description
the optional inferred class alias
Return values
Description
the same value placed in the service cache
Reads the latest completed value and compresses acyclic identity paths.
private
classExtensionResult(
object
$object[,
array<int, object>
$pendingObjects = []]) : array{value: mixed}|null
Description
A cycle retains the input's recorded result without compressing the ambiguous path. A path returning to a pending input SHALL remain unchanged until its callback chain completes.
Parameters
$object
:
object
Description
the original or same-class result identity
$pendingObjects
:
array<int, object>
=
[]
Description
class identities being completed in this resolution
Return values
Description
the wrapped result, or null when no extension completed
Normalizes completed replacement values before recording the current class-extension chain.
private
completeClassExtensions(
array<int, object>
$objects,
mixed
$service) : mixed
Description
This method SHALL follow only recorded results; it SHALL NOT execute another class extension.
Parameters
$objects
:
array<int, object>
Description
the class identities completed in this resolution
$service
:
mixed
Description
the final callback value
Return values
Description
the latest completed replacement value
Updates existing acyclic identities to their latest completed result.
private
compressClassExtensionPath(
array<int, object>
$objects,
array{value: mixed}
$result) : void
Parameters
$objects
:
array<int, object>
Description
the recorded identities visited during lookup
$result
:
array{value: mixed}
Description
the latest completed result
Invokes an available factory and applies the entry's extensions to its value.
private
createExtendedService(
string
$id,
mixed
$factory,
array<string|int, mixed>
$extensions) : mixed
Parameters
$id
:
string
Description
the requested service identifier
$factory
:
mixed
Description
the factory definition, which may be absent or non-callable
$extensions
:
array<string|int, mixed>
Description
the provider's extension definitions
Return values
Description
the completed extension result, including null
Executes an ID callback and identifies whether it also completed a class-ID extension.
private
executeIdentifierExtension(
string
$id,
mixed
$service,
callable
$extension) : array{mixed, list
Parameters
$id
:
string
Description
the requested service identifier
$service
:
mixed
Description
the previous entry value
$extension
:
callable
Description
the callable registered under this identifier
Return values
Description
the callback result and any completed class identity
Follows recorded identities while retaining a bounded result for cyclic paths.
private
followClassExtensionResults(
object
$object,
array{value: mixed}
$initial) : array{array{value: mixed}, list
Parameters
$object
:
object
Description
the first identity to follow
$initial
:
array{value: mixed}
Description
the first identity's recorded value
Return values
Description
the completed result and safely compressible identities
Checks existing entries using metadata already loaded by the caller.
private
hasEntry(
string
$id[,
array<string, mixed>
$factories = []][,
array<string, mixed>
$extensions = []]) : bool
Parameters
$id
:
string
Description
the entry identifier
$factories
:
array<string, mixed>
=
[]
Description
the available factory definitions
$extensions
:
array<string, mixed>
=
[]
Description
the available extension definitions
Return values
Description
whether a resolved entry or callable definition exists
Infers a concrete-class alias only when it cannot replace another entry or definition.
private
inferredClassAlias(
string
$id,
mixed
$service,
array<string|int, mixed>
$factories,
array<string|int, mixed>
$extensions) : string|null
Parameters
$id
:
string
Description
the identifier being resolved
$service
:
mixed
Description
the completed service value
$factories
:
array<string|int, mixed>
Description
the provider's factory definitions
$extensions
:
array<string|int, mixed>
Description
the provider's extension definitions
Return values
Description
the available class alias, or null when none should be registered
Reads factory and extension metadata in provider order without executing their callables.
private
readProviderDefinitions(
string
$id) : array{array, array}
Parameters
$id
:
string
Description
the identifier used to report a metadata failure
Return values
Description
factories followed by extensions
Looks up an object identity without inspecting or executing extension definitions.
private
registeredClassExtensionResult(
object
$object) : array{value: mixed}|null
Parameters
$object
:
object
Description
the class-extension input or same-class result
Return values
Description
the wrapped completed value, or null when absent
Records completed results while leaving different-class replacements eligible for their own extensions.
private
rememberClassExtensionResult(
array<int, object>
$objects,
mixed
$service) : void
Parameters
$objects
:
array<int, object>
Description
identities whose completed class-extension result can be reused
$service
:
mixed
Description
the final value of the completed extension chain
Resolves an entry once, preserving construction failures and the final cached value.
private
resolveUncachedEntry(
string
$id) : mixed
Parameters
$id
:
string
Description
the requested service identifier
Return values
Description
the fully extended service value
Restores the caller's path after resolution or releases an empty execution context.
private
restoreResolutionPath(WeakMap<object, array<int, array{container: self, id: string}>>
$paths,
object
$context,
array<int, array{container: self, id: string}>
$previousPath) : void
Parameters
$paths
:
WeakMap<object, array<int, array{container: self, id: string}>>
Description
active paths by execution context
$context
:
object
Description
the current Fiber or main-context wrapper
$previousPath
:
array<int, array{container: self, id: string}>
Description
the path before this entry was resolved