yokai/dependency-injection
yokai/dependency-injection is a lightweight Laravel/PHP package for managing class dependencies and service resolution, helping you bind, resolve, and inject objects through a simple container to keep application code cleaner and more testable.
This is not a framework or an autowiring replacement — it is a small set of utility classes for manipulating the Symfony DI container: typically compiler passes, service-locator helpers, and container bootstrapping conveniences for tests. Treat it as a toolbox you dip into, not infrastructure you build on.
Minimal steps
composer require yokai/dependency-injection and immediately read composer.json — the symfony/dependency-injection and PHP constraints tell you more about compatibility than the README will. The last release is September 2024, so expect Symfony 6/7-era APIs.src/ before anything else. The package is small enough that the source is the documentation; you can read every class in a coffee break.tests/ next. With 3 stars and 0 dependents there are no blog posts, no Stack Overflow answers, and no third-party examples — the test suite is your only reliable usage reference.*Bundle.php) or is purely a set of classes you reference from your own compiler passes. That distinction determines whether you register anything in config/bundles.php at all.First use case
The highest-value entry point is test-time container bootstrapping: building a minimal, real container in a unit test instead of booting the whole kernel or hand-wiring mocks. If the package offers a container helper, wire one service, resolve it, assert on it — that is a ten-minute proof that justifies the dependency. If it only offers compiler passes, your first use case is decorating or collecting tagged services without writing the boilerplate Reference/Definition juggling yourself.
Use it at the wiring layer, never in domain code. Everything this package touches belongs in Kernel::build(), a bundle extension, or a compiler pass — code that runs before the container is compiled. If a class from this package shows up in a controller or a domain service, you have leaked infrastructure.
Compiler pass registration. If the package provides passes, register them explicitly rather than relying on bundle magic, so the ordering is visible:
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(
new SomePass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
10
);
}
Prefer TYPE_BEFORE_OPTIMIZATION unless the pass needs resolved service ids; anything that inspects tags generally wants to run before ResolveTaggedIteratorArgumentPass and friends.
Service locator as an escape hatch. The classic use for DI utilities in Symfony is turning a wide constructor (eight dependencies, half of them optional) into a lazy locator behind a narrow interface. Use ServiceSubscriberInterface on the consumer so the locator contents are declared, not hidden — the whole point is that the dependency set stays greppable.
Decorator re-registration is the other high-traffic pattern: wrap a service, re-register it under the original id with setDecoratedService(). This is how you add logging, caching, or retry around an existing definition without touching the class. If the package offers helpers here, they are usually thin wrappers over Definition/Reference — adopt them only if they read better than the three lines they replace.
Tagged iterators over $container->findTaggedServiceIds(). If you are writing a pass by hand, let Symfony's !tagged_iterator do the collection and keep the pass limited to validation (duplicate tags, missing required attributes). Passes that build arrays of Reference objects are a maintenance liability.
The Laravel translation, since that is your day job. Laravel has no compilation step, so these patterns map onto different primitives: service locators become app()->make() inside a small resolver class or contextual binding ($this->app->when(Foo::class)->needs(Bar::class)->give(...)); decorated services become $this->app->extend(Foo::class, fn ($service) => new LoggingFoo($service)); tagged iterators become $this->app->tag([...], 'reports') plus $this->app->tagged('reports'). If you are evaluating this package to bring its patterns back to Laravel, the ideas port cleanly; the classes do not.
Pin the version and assume you may own it. Three stars, zero dependents, one release cadence. There is no bus factor beyond the maintainer. That is fine for a 200-line utility package — but lock the version in composer.json, and before adopting, skim the source for anything that would be a five-minute rewrite. Often it is.
Private services and $container->get(). Symfony services are private by default; after compilation, get() on a private id throws. If the package's test helpers resolve services directly, you need public aliases or a service locator — and you will only find out at runtime, not at compile time. Run bin/console lint:container in CI to catch wiring errors before a request does.
Compilation caching will waste an afternoon. After changing a compiler pass, var/cache must be cleared — bin/console cache:clear in dev, a fresh cache directory per test run in CI. A pass that "doesn't work" is almost always a stale cache in --env=test.
Container state bleeds between tests. If you reuse a compiled container across test cases, any set()/mutation persists. Build the container per test (or per test class) and keep the setup in a trait. This is the number-one source of "passes alone, fails in suite" bugs with container utilities.
Static container access is a trap outside bootstrap. If the package offers a global/static accessor, restrict it by convention to test bootstrapping and console entry points. A static Container::get() reachable from application code will be reached from application code, and then your dependency graph is decorative.
Watch the Symfony version constraint, not the package version. A 2024-09 release can still pin an older symfony/dependency-injection minor that conflicts with your app. Run composer why-not symfony/dependency-injection if the resolve fails; the answer is usually here.
Debugging toolkit. bin/console debug:container <id> shows the resolved definition and its tags; debug:autowiring confirms aliases; lint:container validates arguments. For compiler passes specifically, debug:container --tag=your_tag is faster than adding dump() calls inside the pass.
Extension points to verify before you depend on them. Grep the source for CompilerPassInterface, ServiceSubscriberInterface, and any abstract classes — those are the intended seams. If everything is final with no interfaces, the package is a set of helpers to copy, not a library to extend, and you should plan accordingly.
How can I help you explore Laravel packages today?