sergix44/container
A lightweight PSR-11 compatible dependency injection container for PHP, with bindings, singletons, aliases, and automatic constructor resolution. It keeps service wiring simple and testable for small apps and libraries, without requiring a full framework.
Architecture fit. sergix44/container is a framework-agnostic, PSR-11-compliant DI container with singleton/transient bindings, closure factories, lazy resolution, and reflection-based constructor autowiring. That is a well-understood shape, but it overlaps almost completely with Illuminate\Container, which Laravel already ships and which itself implements Psr\Container\ContainerInterface. Inside a Laravel application, adopting this package means running a second container alongside the framework's composition root — not replacing it. The architecture fit is therefore poor for the "swap out Laravel's container" story and good only for narrow, well-bounded use cases: a standalone library or package that must not depend on illuminate/*, a CLI/cron entrypoint bootstrapped without the HTTP kernel, a queue consumer or serverless handler with a hand-rolled bootstrap, or an extracted service in a monorepo where non-Laravel PHP code needs a container. The package's stated benefit — "no tight framework coupling" — is precisely the property a Laravel app is not optimizing for.
Integration feasibility. Composer-installable, MIT-licensed, small API surface, single dependency on psr/container in all likelihood. The integration work is not the container itself; it is the bridge. Any real adoption requires an adapter that (a) implements Psr\Container\ContainerInterface, (b) resolves from Laravel first and delegates to the package second (or vice versa, with an explicit ownership rule), and (c) translates exception types so Laravel's error handling and telemetry still recognize resolution failures. Feasible in a day or two of engineering, but it is bespoke glue that becomes internal code you own forever. Note the gap list versus Illuminate\Container: no service providers, no package auto-discovery, no contextual binding, no tagged bindings, no deferred providers, no method/attribute injection, no scoped (request) lifetimes, no events around resolution. Any of these that the team currently relies on becomes either a regression or more glue.
Technical risk. The dominant risk is not code quality — it is supply-chain and sustainability. Five stars, zero dependents, a single release dated 2024-06-18, and no visible bus factor beyond one maintainer. There is no evidence of a published security policy, an active CI matrix, or a PSR-11 conformance test suite run. Treat the package as at-risk from day one: assume you may need to fork it and vendor it, and price that in (a ~150–300 LOC container is cheap to reimplement in-house, which caps the downside but also caps the strategic value of adopting it). Secondary risks are concrete and testable: psr/container major-version conflict against Laravel's constraint; reflection/autowiring behavior on unresolvable scalar parameters; circular-dependency handling; and, under Octane or long-lived workers, singleton state leaking between requests.
Key questions.
psr/container major (^1 or ^2) does it require, and does that intersect cleanly with the current Laravel version's constraint?bind/singleton?Recommendation: Adopt conditionally, scoped narrowly. Do not pursue it as a replacement for Illuminate\Container in an existing Laravel application — the migration cost exceeds the benefit and the blast radius is the whole app. Evaluate it only for (a) a standalone package that must stay framework-free, or (b) a non-Laravel entrypoint, and only behind an internal adapter with a documented ownership rule. Otherwise the correct TPM call is to note that the framework already provides a PSR-11 container and decline.
Stack fit. Viable on current Laravel (10/11/12) and PHP 8.1+, but the fit is orthogonal rather than additive — it does not make Laravel better, it introduces a parallel resolution path. Frame the fit question as "which code is allowed to be resolved by which container," not "should we use this container." Best-fit targets, ranked:
| Target | Fit | Rationale |
|---|---|---|
Standalone composer package (no illuminate/*) |
Strong | Realizes the framework-agnostic value proposition |
| CLI / cron / worker bootstrap outside HTTP kernel | Moderate | Useful, but Laravel's bootstrap is usually still cheaper |
| Serverless handler or extracted microservice | Moderate | Depends on whether the service is Laravel or plain PHP |
| Inside an existing Laravel app's service graph | Weak | Duplicate semantics, dual-container bugs |
Replacing Illuminate\Container |
Reject | Disproportionate risk, no material benefit |
Migration path. Do not frame this as a migration. Frame it as coexistence with a single declared composition root. Laravel stays the root; the package is introduced bottom-up in exactly one leaf. Concretely: extract the target component behind an interface, register its dependencies in sergix44/container inside a thin bootstrap, and expose a ContainerInterface adapter that consults Laravel first for framework services (config, logger, DB) and the package for component-local services. Keep the adapter in first-party code, not as a fork. If at any point the adapter grows beyond ~200 lines, that is the signal that the boundary is wrong and the component should just use Laravel's container.
Compatibility. PSR-11 guarantees only get/has plus throw-on-not-found semantics; everything else — definition sources, extensions, delegation, compile/optimize steps — is outside the interop contract. So "PSR-11 compliant" buys the narrowest possible interop and should not be sold internally as portability. Two compatibility checks are gating and must be run before any commitment:
composer why-not sergix44/container <laravel-version> and a lockfile diff to confirm psr/container version intersection.Also verify PHP version constraints against your CI matrix and confirm behavior under Octane (or your long-lived worker runtime) if applicable.
Sequencing.
ContainerInterface, exception translation, and a written ownership rule ("framework services → Illuminate; component services → sergix44").Maintenance. Single maintainer, zero dependents, one release, five stars. The realistic planning assumption is that upstream maintenance stops without notice. Plan for one of two contingencies: vendor the source into the monorepo with a pinned commit, or be prepared to fork. Because the package is small, forking is cheap — but "cheap to fork" is also the argument against adopting it, so state the trade-off explicitly rather than letting it sit unexamined. Budget a recurring line item for quarterly upstream checks and security review of the pinned version.
Support. No commercial support, no vendor relationship, no SLA. Escalation path terminates at the maintainer's GitHub issues and then at your fork. This is acceptable for a low-criticality leaf component and unacceptable for anything on the request path of a revenue-critical service. Communicate that boundary to stakeholders in exactly those terms; "it's MIT and we can fix it ourselves" is true but should be a signed-off decision, not an assumption.
Scaling. Technical scaling is a non-issue at realistic sizes — the container is small and resolution is O(depth), provided reflection metadata is cached (verify). The scaling problem is organizational: every additional team that adopts it adds a second definition of "how services are wired," and divergence between Laravel-idiomatic wiring and package-idiomatic wiring compounds. Cap adoption at one or two clearly bounded surfaces. If adoption spreads past that, the correct move is to consolidate onto the framework container and retire the package, and that decision is much cheaper to make early than late.
Failure modes. Ranked by likelihood × impact:
psr/container blocking a Laravel or PHP upgrade. Medium likelihood, high annoyance, visible to the whole repo.Ramp-up. The API is genuinely small: a competent PHP engineer reads it in an hour and is productive in a day. That is the good news and also the trap — the framework cost is trivial while the convention cost is not. Realistic ramp-up is hours for the API, two to three weeks for the discipline of knowing which container owns which service, why a given binding lives where it does, and how to debug a resolution that went to the wrong place. Mitigate with a one-page internal convention note checked into the repo alongside the adapter, covering: ownership rule, exception translation, where bootstrapping happens, and the explicit list of framework features (providers, tags, contextual binding, scoped lifetimes) that are unavailable. Onboarding without that note will produce exactly the dual-container bugs listed above.
How can I help you explore Laravel packages today?