Skip to documentation

ServiceProviderContainer

Class
implements ContainerInterface
Final: Yes

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.

Table of Contents

Interfaces

ContainerInterface

Extends the PSR-11 ContainerInterface to provide a consistent, domain-specific container interface for the FastForward ecosystem.

Properties

$cache

Cache of resolved services keyed by their identifier or class name.

 : array<string, mixed>
$classExtensionResults

Final class-extension values indexed by original and same-class resulting object identities.

 : WeakMap<object, array{value: mixed}>|null
$resolutionPaths

Active provider entries grouped by Fiber, or by wrapper in the main context.

 : WeakMap<object, array<int, array{container: self, id: string}>>|null
 : ServiceProviderInterface
$wrapperContainer

The container instance used for service resolution and extension application.

 : ContainerInterface

Methods

__construct()

Constructs a new ServiceProviderContainer instance.

 : mixed
get()

Retrieves a service from the container by its identifier.

 : mixed
has()

Determines if the container can return an entry for the given identifier.

 : bool
applyConcreteClassExtension()

Reuses a completed result or executes one extension for the current concrete class.

 : array{mixed, list}
applyIdentifierExtension()

Runs an ID extension against a reused value without repeating an already completed class-ID extension.

 : array{mixed, list}
applyServiceExtensions()

Applies ID-based and concrete-class extensions, retaining each returned value.

 : mixed
assertEntryIsNotResolving()

Rejects re-entry into this provider's active service while allowing ordered fallback to other providers.

 : void
cacheResolvedService()

Stores the final value under its requested ID before adding any inferred alias.

 : mixed
classExtensionResult()

Reads the latest completed value and compresses acyclic identity paths.

 : array{value: mixed}|null
completeClassExtensions()

Normalizes completed replacement values before recording the current class-extension chain.

 : mixed
compressClassExtensionPath()

Updates existing acyclic identities to their latest completed result.

 : void
createExtendedService()

Invokes an available factory and applies the entry's extensions to its value.

 : mixed
executeIdentifierExtension()

Executes an ID callback and identifies whether it also completed a class-ID extension.

 : array{mixed, list}
followClassExtensionResults()

Follows recorded identities while retaining a bounded result for cyclic paths.

 : array{array{value: mixed}, list}
hasEntry()

Checks existing entries using metadata already loaded by the caller.

 : bool
inferredClassAlias()

Infers a concrete-class alias only when it cannot replace another entry or definition.

 : string|null
readProviderDefinitions()

Reads factory and extension metadata in provider order without executing their callables.

 : array{array, array}
registeredClassExtensionResult()

Looks up an object identity without inspecting or executing extension definitions.

 : array{value: mixed}|null
rememberClassExtensionResult()

Records completed results while leaving different-class replacements eligible for their own extensions.

 : void
resolveUncachedEntry()

Resolves an entry once, preserving construction failures and the final cached value.

 : mixed
restoreResolutionPath()

Restores the caller's path after resolution or releases an empty execution context.

 : void
Properties

$cache

Private

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.

$classExtensionResults

Private

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.

$resolutionPaths

Private Static

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.

$wrapperContainer

Private Read-only

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.

Methods

__construct()

Public

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.

get()

Public

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

Tags
throws

Description

if neither a callable factory nor an ID-based extension exists

throws

Description

if factory or extension resolution fails or a dependency cycle exists

Return values

Description

the service instance associated with the identifier

has()

Public

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

Tags
throws

Description

if provider metadata cannot be read

Return values
bool

Description

true if the entry exists, false otherwise

applyConcreteClassExtension()

Private

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
array{mixed, list}

Description

the concrete-class result and any newly completed input identity

applyIdentifierExtension()

Private

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
array{mixed, list}

Description

the ID-extension result and identities completed by a class-ID extension

applyServiceExtensions()

Private

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

Tags
throws

Description

if an extension callable fails during execution

Return values

Description

the final entry value

assertEntryIsNotResolving()

Private

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

Tags
throws

Description

if this provider is already resolving the same identifier

cacheResolvedService()

Private

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

classExtensionResult()

Private

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
array{value: mixed}|null

Description

the wrapped result, or null when no extension completed

completeClassExtensions()

Private

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

compressClassExtensionPath()

Private

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

createExtendedService()

Private

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

executeIdentifierExtension()

Private

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
array{mixed, list}

Description

the callback result and any completed class identity

followClassExtensionResults()

Private

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
array{array{value: mixed}, list}

Description

the completed result and safely compressible identities

hasEntry()

Private

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
bool

Description

whether a resolved entry or callable definition exists

inferredClassAlias()

Private

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
string|null

Description

the available class alias, or null when none should be registered

readProviderDefinitions()

Private

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

Tags
throws

Description

if provider metadata cannot be read

Return values
array{array, array}

Description

factories followed by extensions

registeredClassExtensionResult()

Private

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
array{value: mixed}|null

Description

the wrapped completed value, or null when absent

rememberClassExtensionResult()

Private

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

resolveUncachedEntry()

Private

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

Tags
throws

Description

if neither a factory nor an ID extension can supply the entry

throws

Description

if reading metadata or constructing the entry fails

Return values

Description

the fully extended service value

restoreResolutionPath()

Private

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

Search results