level-2/dice
Dice is a lightweight, dependency-free dependency injection container for PHP. It provides automatic constructor and setter injection, factory callbacks, shared instances, and simple configuration for decoupled, testable applications.
Architecture fit
Dice is a standalone, framework-agnostic DI container built around reflection-based autowiring, construction rules, shared instances, and callbacks. That design is a poor fit for application-level use inside Laravel, because Illuminate\Container already provides constructor autowiring, singleton/scoped bindings, contextual binding, tagged bindings, and deep integration with the framework's resolution pipeline (controllers, jobs, listeners, middleware, console commands). Adopting Dice app-side creates two parallel composition roots with overlapping semantics and no shared lifecycle. The credible fit is narrower: as the internal container for a framework-agnostic domain or extraction candidate (a package we want to run outside Laravel), or as a stopgap in a legacy PHP codebase being migrated toward Laravel. In those contexts Dice's small API surface and zero-config autowiring are genuinely useful.
Integration feasibility
Feasible but only as an opt-in, bounded integration. The realistic pattern is to register a configured Dice instance as a Laravel singleton and use it exclusively inside a designated module boundary, with a rule or callback that delegates unresolvable dependencies back to the Laravel container (app()), so Laravel-managed services (config, loggers, HTTP clients) remain the single source of truth. A spike to validate this bridge, PSR-11 conformance (verify — Dice's primary API is create(), and PSR-11/get() support must be confirmed rather than assumed), and PHP/Laravel version constraints against the 2023-01-17 release is roughly 2–3 engineer-days.
Technical risk
#[Inject]-style attributes, facades, app() helpers) do not translate; interfaces without explicit rules fail at runtime, not at boot.Key questions
Illuminate\Container cannot — is this a genuine extraction/portability requirement or incidental complexity?php artisan serve?Stack fit
Composer-installable, no extensions, no framework coupling — it will install cleanly. The misfit is conceptual, not mechanical: Laravel expects to be the composition root. Treat Dice as a module-scoped container behind an interface, never as a replacement for Illuminate\Container and never as the resolver for framework-managed entry points.
Migration path Boundary-first and incremental. Do not attempt a container swap. Identify one module with a clean public API (domain services, an extracted package, a bounded-context library). Stand up Dice inside it, expose the module to Laravel through a single service provider that binds the module's entry points into the Laravel container, and route Dice's unresolved dependencies back to Laravel via a delegate rule. If the driver is legacy modernization, invert the flow: use Dice in the legacy layer first, then progressively repoint bindings at Laravel as classes migrate — Dice becomes scaffolding that is deleted at the end, not a permanent layer.
Compatibility Validate three things before committing: the composer constraints of the 2023-01-17 release against our PHP runtime, PSR-11 conformance (or a thin adapter if we standardize on that interface), and behavior under the framework's scoped/long-lived lifecycle. Laravel's contextual bindings and attribute-based injection have no Dice equivalent, so any class relying on them must stay in the Laravel container or be refactored to explicit rules.
Sequencing
Dice\Dice) to prevent drift.Maintenance The codebase is small and stable, and the low release cadence is as much a signal of maturity as of neglect — but we inherit maintenance risk by default. Budget ownership: assign an internal owner for the dependency, pin the version, and treat any upstream abandonment as a planned vendor-and-patch rather than an incident.
Support Community-only (GitHub issues); no commercial support or security advisory process. Our team must be able to debug reflection-based resolution failures without upstream help. Because it is a contained component, the blast radius of a bug is one module rather than the whole application — provided the boundary is enforced.
Scaling Reflection-based resolution is per-call, so the cost scales with resolution frequency, not request count. Push all hot-path services to shared instances, resolve once at boot where possible, and benchmark the pilot module's p95 before widening. Under Octane or long-running workers, explicitly reset shared state on request boundaries and watch for monotonic memory growth from retained instance graphs.
Failure modes
Ramp-up The API is small enough to learn in under a day, so the cost is not Dice itself — it is the second mental model. Engineers must learn when to use each container and how the bridge behaves, and onboarding docs must state the boundary rule explicitly. Expect the real ramp-up cost to land on reviewers and on whoever owns the test harness, where dual-container mocking is the most common source of friction.
How can I help you explore Laravel packages today?