illuminate/container
The Illuminate Container is Laravel's powerful dependency injection container. It manages class dependencies, binds and resolves services, supports contextual and method injection, and provides a simple, flexible foundation for building decoupled PHP applications.
Architecture fit
illuminate/container is a DI/IoC container: it resolves constructor dependencies, binds interfaces to implementations, and manages singleton lifecycles. As a TPM, treat it as plumbing, not a product surface — it earns its place because it is the substrate for testability (swap bindings for mocks), modularity (interface bindings per feature), and third-party substitutability (swap a payment gateway by rebinding one interface).Psr\Container\ContainerInterface) but its differentiating capabilities — tag()/tagged(), when()->needs()->give() contextual binding, extend(), call() method injection, makeWith() — are Laravel extensions. Any architecture that depends on those is coupled to Laravel, and any portability claim must be scoped to get/has only.Integration feasibility
laravel/framework; no install, no wiring, no decision to justify beyond "we're on Laravel."composer require illuminate/container works, but you inherit illuminate/contracts and a small dependency tail, and you take on composition-root responsibilities Laravel normally handles for you (bootstrap, singleton registration, request/console lifetimes).laravel/framework. You cannot patch it in place and have the patch survive; contributions go upstream to the monorepo. That converts any necessary fix into a monorepo PR, a release-train wait, or a vendored fork — a real schedule risk if this package ever becomes load-bearing in a non-Laravel context.Technical risk
Container::getInstance() / setInstance() mutate process-wide state. In long-running workers (queue consumers, Octane-style servers, Horizon) this is the primary leakage and cross-tenant-contamination vector. Mitigation is a policy decision, not a code fix: ban setInstance() in application code and enforce by static analysis.CircularDependencyException surfaces during resolution — i.e., in staging or production, on the code path that first asks for the object. No compile-time graph validation is provided.tag()/extend()/when() and later want out of Laravel face a non-trivial rewrite. Encode "PSR-11 subset only" as an architecture rule if exit optionality matters.laravel/framework monorepo, not the split package; Dependents: 0 is a measurement artifact of subtree splits, not evidence of no adoption. The reported last release date (2026-10-27) is in the future relative to normal reporting and should be reconciled against the actual tag history before anyone cites it in a decision memo. Do not use star count or dependent count as adoption evidence here; use "bundled with every Laravel install" instead.composer.json rather than taken second-hand.Key questions
extend()), or is a PSR-11 subset sufficient? The answer determines our exit cost.Container::getInstance() / setInstance(), and how is it enforced? Without an enforced rule, long-running workers are a latent incident.Stack fit
Migration path
bind()/singleton()/instance(), run both in parallel behind a thin adapter, migrate new code first, and delete the old container only after the graph is fully served. Budget time for the resolution-order and lifecycle semantics that differ (singleton scope, deferred resolution, extend() post-processing).Compatibility
get()/has() interop; it does not buy feature parity. Any custom resolver, decorator, or third-party integration should target ContainerInterface so it survives a container swap.tagged(), when(), and build() are Laravel-only and will break under a different implementation.illuminate/contracts version alignment if standalone — mismatches with the Laravel major in use are a common source of confusing resolution errors.Sequencing
illuminate/contracts compatibility) against the platform matrix. This gates everything else.setInstance().make() calls during the pilot to find hot paths and accidental double-resolution before they become performance surprises.Maintenance
Support
Scaling
tagged() collections becoming unbounded lists that no one can enumerate.Failure modes
| Failure | Signal | Blast radius | Mitigation |
|---|---|---|---|
CircularDependencyException |
Fatal at resolve time on a specific code path | Feature-level, sometimes request-fatal | Inject interfaces; lazy/deferred make(); assert graph health in CI |
Global instance leakage (setInstance()) |
Cross-tenant data bleed, stale config in workers | Severe; can be silent and non-deterministic | Ban the API; per-request container; reset in worker bootstrap |
| Unbound interface | BindingResolutionException / Target [X] is not instantiable |
Localized, usually caught in dev | Bind at the module boundary; fail fast in tests |
| Silent double-instantiation | Unexpected state reset, duplicate side effects | Moderate; confusing to diagnose | Explicit singleton vs transient policy; resolved() checks in diagnostics |
| Portability break on framework exit | Rewrite of tag()/when()/extend() usage |
Strategic, not operational | PSR-11 subset rule for any code that must outlive Laravel |
| Metadata misreading (stars/dependents/release date) | Wrong business case presented to leadership | Credibility | Cite "bundled with Laravel," not repo metrics |
Ramp-up
bind, singleton, instance, make, resolve) cover ~90% of daily use. A developer is productive in an afternoon.extend() decorators, makeWith() for parameterized resolution, call() method injection, resolver hooks, and overriding make()/build(). These should be gated behind review, or they become clever one-offs nobody else can maintain.setInstance(), tag() in portable code, constructor-time make() that hides cycles — plus one worked module as a reference implementation. Half a day of enablement, and it prevents most of the failure modes above.How can I help you explore Laravel packages today?