Skip to content

Latest commit

 

History

History
216 lines (178 loc) · 13.5 KB

api-reference.md

File metadata and controls

216 lines (178 loc) · 13.5 KB

API Reference

← Back to index

Public API

Every class in src/ is marked either @api or @internal.

@api — covered by semantic versioning. These will not break within a major version:

Class
Container the container itself (final)
ContainerInterface the contract to type-hint against
FullContainerInterface deprecated empty alias of ContainerInterface; removed at 3.0
ContextualBindingBuilder returned by when()
CompilationReport, CompilationSkipReason returned by compileReport()
ValidationReport, ValidationIssue, ValidationProblem returned by validate() — see performance
ClassSource where the compile calls find their classes — see performance
Console\Cli vendor/bin/gacela-container, @internal — see performance
DependencyNode returned by dependencyGraph()
Attribute\Inject, Attribute\Singleton, Attribute\Factory, Attribute\Lazy the PHP 8 attributes
Exception\ContainerException
Exception\CircularDependencyException
Exception\DependencyNotFoundException
Exception\DependencyInvalidArgumentException

For the exceptions, the class and its PSR-11 interface are stable. Exception messages are not — do not parse or assert on them.

@internal — not covered. AliasRegistry, BindingResolver, DefinitionLoader, DependencyCacheManager, DependencyResolver, DependencyTreeAnalyzer, FactoryManager, FuzzyMatcher, InstanceRegistry, PlanRegistry, and TagRegistry are implementation details of Container. They may change signature, behaviour, or disappear entirely in any release, including a patch. Do not import them.

What the interface guarantees

Every instance method below is declared on ContainerInterface. Type-hint it and you get the whole surface — there is nothing left that only the concrete class can do:

public function __construct(
    private ContainerInterface $container,
) {
}

It also extends ArrayAccess, so $c[Id::class], isset(), assignment and unset() are part of the contract rather than a concrete-class convenience. createScope() is typed static, so a scope of a decorator is a decorator.

Through 1.x the interface was frozen — nothing could be added to it — which left twelve methods reachable only through final class Container, and 1.5 answered that additively with FullContainerInterface. 2.0 merged them. FullContainerInterface survives as a deprecated empty alias, so a 1.5 type-hint keeps compiling and keeps accepting a Container; it is removed at 3.0. Nothing is added to ContainerInterface within 2.x.

What remains on Container alone is what an instance contract cannot express: the constructor, and the static methods create(), loadCompiledCache(), loadCompiledFactories() and resetStaticCaches() — there is no instance that owns that state to ask, and so nothing for a decorator to forward.

Two caveats. getStats() is on the interface, but the shape of the array it returns is not covered by backward compatibility — treat it as debug output, or use stats(), whose shape is covered. And the memory figure in both is the whole PHP process, not the container; every other field is container-scoped, so it is the one number that does not answer "what does this container hold". See introspection.

Container methods

Method Description
get(string $id): mixed Retrieve or create a service
getOrFail(string $id): mixed Like get(), but throws when the id resolves to null
make(string $className, array $parameters = []): object Resolve a class to a typed, non-null instance; $parameters override constructor args by name
has(string $id): bool PSR-11: whether get() will resolve the id — includes autowirable classes
afterResolving(string $id, Closure $callback): void Run a callback after the id is resolved ($instance, $container). A class/interface id also matches nested dependencies before the receiving constructor runs; any other id matches exactly
bound(string $id): bool Whether the id was explicitly registered — a binding or a stored instance (alias-aware)
provides(string $id): bool Whether this container or an ancestor already owns the id — a binding, a stored instance, or a resolved singleton
bindIf(string $abstract, string|callable|object $concrete): void Bind only if not already bound
singletonIf(string $abstract, string|callable|object|null $concrete = null): void Singleton-bind only if not already bound
bind(string $abstract, string|callable|object $concrete): void Register a binding after construction
singleton(string $abstract, string|callable|object|null $concrete = null): void Register a binding resolved once and reused
lazy(string $abstract, string|callable|null $concrete = null): void Register a binding whose construction is deferred to first use — see bindings
load(array $definitions, ?callable $onRegistered = null): array Register services from a definitions array; returns the ids it registered — see definitions
loadFile(string $file, ?callable $onRegistered = null): array Load definitions from a .php file returning an array, a .json file, or a .yaml/.yml one when a YAML parser is installed; returns the ids it registered
set(string $id, mixed $instance): void Register a service
remove(string $id): void Remove a stored service or evict a locally cached singleton
forget(string $id): void Drop what was built for a service, including cached singleton values, and keep how to build it: a closure-registered service is built again on the next get(), a factory() is left as it is. On Container only, not on ContainerInterface, which does not grow within 2.x; a wrapper forwards it by hand
resolve(callable $callable, array $parameters = []): mixed Execute a callable with dependency injection; $parameters override args by name
factory(Closure $instance): Closure Mark a service as a factory (new instance each time)
extend(string $id, Closure $instance): Closure Wrap/modify a service
protect(Closure $instance): Closure Prevent closure execution
getRegisteredServices(): array Get all service IDs
isFactory(string $id): bool Check if a service is a factory
isFrozen(string $id): bool Check if a service is frozen
getBindings(): array Get all bindings
warmUp(array $classNames): void Pre-resolve dependencies. Takes a list only: warming resolves, so it deliberately does not accept a ClassSource
compile(array $classNames): array Warm up and return compiled constructor plans. Container also takes a ClassSource, which describes instead of warming
writeCompiledCache(array $classNames, string $file): void Compile plans and write them to a PHP cache file, fingerprinted against the files they came from — see staleness. Container takes an optional third ?string $buildStamp
writeCompiledFactories(array $classNames, string $file, ?string $buildStamp = null): array Generate new expressions for statically-decidable classes; returns those compiled
withSelfReference(ContainerInterface $facade): self Hand $facade to service closures instead of this container — for decorators
validate(array|ClassSource $classNames): ValidationReport Prove these classes resolve without resolving them
compileReport(array $classNames): CompilationReport What the generator makes of these classes, and why it refuses the rest — see performance. Container also takes a ClassSource
useCompiledFactories(array $factories): void Use generated factories as a fast path
alias(string $alias, string $id): void Create an alias for a service
tag(string|array $ids, string $tag): void Group service ids under a tag (accumulates, dedupes); a map gives entries keys
tagged(string $tag): iterable Lazily resolve all services under a tag, in insertion order; keyed entries under their key
taggedByKey(string $tag, string $key): mixed Resolve the one entry under $key; throws naming the known keys if there is none — see tags
taggedKeys(string $tag): array The keys a tag can be asked for, in insertion order
dependencyGraph(string $className): DependencyNode The dependency graph as a tree — depth, the parameter each child satisfies, and cycles marked rather than thrown — see cookbook
createScope(): static A child container inheriting this one's registration without copying it — see scopes
stats(): ContainerStats Container statistics as a readonly object; shape is covered by BC. Its processMemoryBytes is process memory, not this container's — see introspection
getStats(): array Same numbers as an array (return shape is not covered by BC). Superseded by stats()
getDependencyTree(string $className): array Every class a given class depends on, flat and deduplicated — despite the name, a list rather than a tree. Use dependencyGraph() for structure
when(string|array $concrete): ContextualBindingBuilder Define contextual bindings for specific classes (needs() accepts a type or a $paramName)
offsetGet / offsetSet / offsetExists / offsetUnset ArrayAccess with string or integer offsets: lookup, assignment, isset(), unset(). See array access

Static methods

// Quick instantiation without container setup
$instance = Container::create(YourClass::class);

// Clear the reflection caches shared by every container in the process.
// See performance.md#process-global-caches for what is and is not cleared
Container::resetStaticCaches();

// Load compiled constructor plans from a cache file. Entries whose class file
// changed since the cache was written are dropped and fall back to reflection
$plans = Container::loadCompiledCache(__DIR__ . '/cache/container.php');
$container = new Container($bindings, [], $plans);

// Same, for generated `new` expressions
$container->useCompiledFactories(
    Container::loadCompiledFactories(__DIR__ . '/cache/factories.php'),
);

Both take an optional second argument, a build stamp: pass the same value used when the file was written and the whole file is validated in one comparison instead of one stat per class. See staleness.

The constructor

public function __construct(
    array $bindings = [],        // abstract => concrete map
    array $instancesToExtend = [],  // id => list of extension closures
    array $compiledPlans = [],   // from writeCompiledCache() / loadCompiledCache()
    ?PlanCache $planCache = null, // one plan cache shared with unrelated containers
)

PlanCache shares reflection output — constructor plans — between containers that have no parent/scope relationship, and nothing else: bindings, contextual bindings, instances and singletons stay private to each container. See performance.

PlanCache::fromFile($file, ?$buildStamp) and $planCache->writeTo($file, ?$buildStamp) carry it from one process to the next using the same non-empty identifier of the loaded release. Unstamped writes persist only native internal-class plans; user-class plans remain in memory. See performance.

All four parameters are optional and part of the stable API — their names, order, types, and defaults do not change within a major version. Named arguments are safe.

Container is final

Container cannot be subclassed. Extend behaviour by composition instead of inheritance: type-hint an interface and wrap.

Implement ContainerInterface: it declares the whole surface, so the compiler checks the forwarders for you — a method the wrapper forgets is a compile error rather than one that silently goes missing at runtime. A wrapper should also call withSelfReference() on the container it wraps, so service closures receive the wrapper rather than the inner container.

final class LoggingContainer implements ContainerInterface
{
    public function __construct(
        private ContainerInterface $inner,
        private LoggerInterface $logger,
    ) {}

    public function get(string $id): mixed
    {
        $this->logger->debug('resolving', ['id' => $id]);

        return $this->inner->get($id);
    }

    // delegate the rest to $this->inner
}

For per-service changes you usually do not need a wrapper at all — prefer extend() to decorate a single binding, or afterResolving() to run code after an id resolves.

Attributes

Attribute Target Description
#[Inject(class-string)] parameter Inject a specific implementation
#[Singleton] class Instantiate once and reuse
#[Factory] class Create a fresh instance every time
#[Lazy] class Defer construction until first use (PHP 8.4+; eager on 8.3)

See attributes for examples.