Product Decisions This Supports
- Build vs. buy for dependency injection outside the framework. Inside a Laravel application this decision is already made — Laravel's container is free, deeply integrated, and handles contextual binding, tagging, deferred providers, and controller method injection. The real decision this package supports is the inverse: when we extract code into a standalone library, CLI tool, or SDK that must not depend on
illuminate/*, do we hand-roll a container or take a ~1-class PSR-11 implementation? sanmai/di-container makes "buy" cheap and reversible.
- Decoupling / de-frameworking roadmap. If the roadmap contains "extract shared domain package," "ship an open-source SDK," or "make internal tooling usable outside the monolith," a framework-agnostic container is a prerequisite. This package lets extracted packages keep constructor injection without dragging Laravel in as a transitive dependency — a concrete unblocker for those initiatives.
- Library and tooling portfolio hygiene. The package's provenance matters: it was built for Infection's own autowiring needs, i.e., a real developer-tool product, not a demo. That makes it a plausible fit for our own CLI/lint/analysis tooling where startup cost and dependency surface are product requirements, not nice-to-haves.
- Use-case scoping for autowiring semantics. Because it resolves an interface only when exactly one registered factory or builder produces a compatible implementation, it forces an explicit product decision about ambiguity: we either register one obvious implementation per interface or we don't adopt it. That is a design constraint we should accept deliberately, not discover at runtime.
- Vendor-risk budgeting. 7 stars, 0 dependents, single-maintainer, BSD-3-Clause, active through 0.1.12 with a steady stream of small PRs. The correct product posture is "low blast radius, no strategic dependency": acceptable where a swap costs an afternoon, unacceptable anywhere a container failure becomes an incident.
- Not a decision this supports: replacing or wrapping Laravel's container. Any proposal framed that way should be rejected at intake — it would require reimplementing service providers, facades, and framework bootstrapping to no product benefit.
When to Consider This Package
Consider it when:
- The artifact is genuinely outside the framework: a Composer library, an SDK, a standalone CLI, a worker binary, or an internal tool that must run without
illuminate/*.
- Requirements are constructor-only injection, singleton-per-container semantics, PSR-11 interop, and zero configuration — nothing more.
- The dependency graph is small and acyclic enough that "no circular dependency checks, a minimal test will reveal it" is an acceptable trade.
- Interfaces map unambiguously to one implementation, or you are willing to make that true via the
Builder pattern (which is the genuinely nice piece here: testable, injectable construction objects with a covariant template that PHPStan validates).
- We want a container small enough that an engineer can read the entire implementation before lunch and fork it if the maintainer disappears.
Look elsewhere when:
- The code lives inside a Laravel app or a Laravel package that will be consumed by Laravel apps. Use the framework container; the integration cost here is a net negative.
- You need contextual binding, tagged collections, deferred/lazy providers, parameter binding, decoration, or method/property injection — all explicitly out of scope by design.
- You need constructor variadics or composite/union type resolution; the container intentionally refuses these.
- You need guaranteed support windows, security review, or an SLA. 0 dependents and 7 stars means we are the de facto QA function; budget for reading the source on every upgrade and pinning versions.
- You need non-class service IDs as a first-class concept rather than a bolt-on:
bind() and the bindings constructor parameter work and bypass type validation, but the API surface around them is young (added in 0.1.6/0.1.9/0.1.10) and likely to keep moving.
How to Pitch It (Stakeholders)
To executives: "This is a small, permissively licensed, framework-independent dependency injection container. It is not a candidate to replace Laravel's container — that would be pure cost with no benefit. Its value is narrower and real: it lets us ship libraries, SDKs, and developer tooling that don't drag Laravel along as a dependency, which is what makes those artifacts reusable outside our stack and reduces future re-platforming cost. Cost of adoption is near zero and cost of reversal is measured in hours, so the downside is bounded. The risk we are accepting is maintenance: the project has a single maintainer and effectively no community adoption, so we should pilot it in one non-critical extracted package rather than standardize on it."
To engineering: "It's a PSR-11 container with reflection-based constructor autowiring, singleton reuse, and one deliberate rule: an interface resolves only if there is exactly one registered factory or builder producing a compatible implementation — no guessing, no naming conventions, no background scanning. Complex construction goes in Builder classes that are themselves injectable and unit-testable, with a covariant template so static analysis catches return-type mistakes. set(), bind(), and inject() cover on-the-fly registration, non-class IDs, and pre-built instances. Known trade-offs, by design: constructor-only injection, no variadic or composite type support, and no circular dependency detection. Treat it as a small dependency you own, not one you consume — read it, pin it, and keep the container confined to the composition root of standalone packages where swapping it out is a contained change."