rollerworks/sf-container-injector
A Symfony component that injects services from the container into objects and controllers, enabling lazy setter-based dependency injection without constructor wiring. Useful for legacy code and optional dependencies.
Read this first: the package is archived (last release 2014-03-12, zero dependents). The "getting started" advice for rollerworks/sf-container-injector is therefore conditional: use it only to understand or maintain an existing Symfony 2.x-era application that already depends on it. Do not add it to a new project — Symfony's native container features have fully superseded it for a decade.
What it does, in one sentence: it gives you a small helper that pushes the Symfony service container into arbitrary objects at runtime, so legacy classes that aren't services (entities, value objects, form types, controllers, factories) can pull dependencies out of the container lazily instead of having them constructor-injected.
Minimal steps if you must install it:
composer require rollerworks/sf-container-injector
Check composer.json first — it targets PHP 5.3+ and Symfony 2.x (symfony/dependency-injection ~2.3/2.4). On PHP 7.2+ or Symfony 3+, expect to pin, patch, or fork.
Where to look, in order:
README.md — usually the only usage documentation that exists.src/ — the entire public surface is small: an injector class (something like ContainerInjector) plus a marker/aware interface that your class implements to opt in.Tests/ — the test suite is the most reliable spec for the injection semantics (what happens on a missing service, whether injection is lazy, whether it recurses).First use case: a legacy class that needs one or two services but is instantiated by hand (not by the container):
use Rollerworks\Component\ContainerInjector\ContainerAwareInterface;
class LegacyReportBuilder implements ContainerAwareInterface
{
use ContainerAwareTrait; // or implement setContainer() manually
public function build()
{
$mailer = $this->container->get('mailer');
// ...
}
}
Then, wherever you construct it:
$builder = new LegacyReportBuilder();
$injector->inject($builder); // the package's whole reason to exist
1. Register the injector once, treat it as infrastructure. Wire the package's service in your bundle's services.yml/services.xml and inject the injector, not the container, into the few places allowed to create legacy objects (usually a factory or a controller base class). Centralising it makes the eventual migration grep-able — one service, one call site.
2. Setter injection at construction boundaries. The standard pattern is: create object → inject → use. Do it in the factory that owns the object's lifecycle, immediately after new, so no code path can observe a half-initialised object.
public function createBuilder(): LegacyReportBuilder
{
$builder = new LegacyReportBuilder();
$this->containerInjector->inject($builder);
return $builder;
}
3. Lazy resolution for optional/expensive services. The main historical justification for this pattern: a class needs a service only on a rarely-hit branch (e.g., a PDF renderer inside an exporter). Constructor injection would force the dependency everywhere. Resolving inside the method defers it:
public function export(Report $report)
{
if ($report->isPdf()) {
return $this->container->get('app.pdf_renderer')->render($report);
}
return $report->toArray();
}
Use this deliberately and sparsely. It is a service locator, and the usual service-locator trade-offs apply.
4. Prefer constructor injection wherever the object is a service. The injector is for objects the container cannot own. If the class is registered in services.yml, just autowire it. Mixing both in one class creates two resolution paths and confuses debugging.
5. Test doubling. Because the injector takes the container as a collaborator, unit tests can bypass it entirely — construct the object and call setContainer() with a ContainerBuilder/mock containing only the services under test:
$container = new ContainerBuilder();
$container->set('mailer', $mailerMock);
$builder = new LegacyReportBuilder();
$builder->setContainer($container);
6. Migration bridge. If you're modernising, the highest-value pattern is: add the aware interface to the class, inject via the package today, and mark the class with a TODO to become a real service. Do not expand the injector's reach — every new inject() call is future debt.
7. Modern replacement mapping. When you migrate, these are the drop-in equivalents:
| Old pattern | Native Symfony replacement |
|---|---|
| Container injected into a service | Constructor autowiring (2.8+/3.0+) |
| Container injected into a lazy branch of a service | ServiceSubscriberInterface (3.3+) with a ServiceLocator |
| Setter injection of a specific dependency | @required setter / #[Required] |
| Objects created outside the container | A factory service, or ServiceLocator passed explicitly |
| Container in a controller | AbstractController + getParameter()/typed constructor args |
inject() calls spread from one legacy class to twenty, and the container becomes an untraceable global. If you inherit this, treat every ContainerAwareInterface implementation as a migration TODO and cap the count.$this->container->get('service') on a non-public service throws. This alone usually forces the migration before anything else breaks.ContainerAwareInterface/ContainerAwareTrait were deprecated in Symfony 4.4 and removed in 5.0. If you upgrade Symfony, this package's interface and Symfony's diverge and you'll get confusing "class exists twice" or "method not found" errors. Fork the interface into your own namespace as the first step of a port.composer.json to an old PHP, or vendor/fork the ~2–5 files and fix them. Given the size, forking is usually cheaper than fighting the constraint resolver.inject() is recursive, whether it throws or no-ops on a class that doesn't implement the marker interface, and whether it overwrites an already-set container are all behaviours worth confirming rather than assuming — the 2014 docs won't tell you.debug:container won't show which classes received the container. Add a temporary log line in your factory when chasing a "service not found" that only appears in one code path.ServiceSubscriberInterface will be both shorter and supported.How can I help you explore Laravel packages today?