Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dependency Injection Laravel Package

symfony/dependency-injection

The Symfony DependencyInjection component standardizes and centralizes object construction in your application, offering a flexible service container with autowiring, configuration, and compilation support.

View on GitHub
Deep Wiki
Context7

Getting Started

You will not usually install this component alone; it comes as the engine underneath symfony/framework-bundle. If you are wiring a standalone app, CLI tool, or library, pull it in directly:

composer require symfony/dependency-injection symfony/config

symfony/config is required for the YAML/XML/PHP file loaders. Without it you only get the programmatic API.

Minimum viable bootstrap:

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config'));
$loader->load('services.yaml');
$container->compile();

$mailer = $container->get(Mailer::class);

Where to look first:

  1. The component docs at symfony.com/doc/current/components/dependency_injection.html — read "Compiling the Container" and "Service Container" before anything else.
  2. ContainerBuilder — the mutable container you configure and compile.
  3. services.yaml conventions (_defaults, autowire, autoconfigure, resource) — this is where 90% of your day-to-day edits happen.
  4. bin/console debug:container, debug:autowiring, lint:container if you are on the framework bundle.

First use case: wiring a service layer that has real constructor dependencies (a repository, an HTTP client, a logger) without new-ing them at call sites. Start with a single services.yaml:

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    App\:
        resource: '../src/'
        exclude: '../src/{Entity,Kernel.php}'

Mental model shift from Laravel: there is no AppServiceProvider::register() running at boot. Your services.yaml is the service provider list, and compiler passes are the equivalent of boot() for container-level concerns. The container is compiled once into a dumped PHP class, then it is read-only.

Implementation Patterns

Constructor autowiring is the default, and it is the main pattern. Type-hint an interface or class; the container resolves it by type. No facades, no app() calls. This alone eliminates most resolve()-from-container code.

final class InvoiceSyncer
{
    public function __construct(
        private readonly InvoiceRepository $repository,
        private readonly HttpClientInterface $http,
        private readonly LoggerInterface $logger,
    ) {}
}

Bindings replace repeated scalar plumbing. Laravel devs reach for config() inside services; Symfony devs bind parameters instead:

services:
    _defaults:
        bind:
            $projectDir: '%kernel.project_dir%'
            $apiKey: '%env(STRIPE_KEY)%'
            $logger: '@monolog.logger.billing'

Every service with a $apiKey constructor argument now gets it without repeating config.

Tags + tagged iterators are the extension mechanism. This is how you get Laravel's "collect all implementations" behavior without a registry class:

services:
    App\Export\ExporterInterface:
        tags: ['app.exporter']

    App\Export\ExportManager:
        arguments: [!tagged_iterator app.exporter]

Add #[AutoconfigureTag('app.exporter')] to the interface, or #[AsTaggedItem(priority: 10)] to implementations, and you never touch YAML again. If you need lazy iteration (heavy services, side effects), use !tagged_locator or #[AutowireLocator] and inject a ServiceLocator keyed by service id.

Service locators over injected containers. Never inject the whole ContainerInterface into a service; narrow it:

#[AutowireLocator('app.exporter', indexAttribute: 'key')]
private ContainerInterface $exporters,

Decoration replaces Laravel's extend() and middleware-on-a-binding. This is the cleanest way to add caching, logging, or retry around an existing service:

services:
    App\Mail\CachedMailer:
        decorates: App\Mail\MailerInterface
        arguments: ['@.inner']

The decorator keeps the original service id as an alias, so injection sites are untouched. Add decoration_on_invalid: ignore when the decorated service may not exist in some environments (this is a common source of compile failures).

Compiler passes for anything the container should decide, not the runtime. Registering aliases, validating tag combinations, reordering, or collecting classes into a registry:

final class RegisterExportersPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container): void
    {
        if (!$container->hasDefinition(ExportManager::class)) {
            return;
        }

        $tagged = $container->findTaggedServiceIds('app.exporter');
        // reorder, validate, set arguments, or ->setAlias(...)
    }
}

Put passes in Kernel::build() or $container->addCompilerPass(); use PassConfig::TYPE_BEFORE_OPTIMIZATION unless you have a reason not to.

Factories and synthetic services for things the container cannot construct. factory: [ConnectionFactory, create], #[AutowireCallable] for method references, and synthetic: true for services injected at runtime (e.g. the HTTP kernel or a request object).

Environment-aware config without env-aware code:

parameters:
    redis_dsn: '%env(string:default:redis_url:REDIS_URL)%'

In 6.4+/7.x these are resolved at runtime when used in service arguments, at compile time when used in parameters that affect structure. Know which one you are writing.

Dumped container in production. PhpDumper outputs a plain PHP class; cache it keyed on config mtime, and preload/warm it in your deploy step. Cold-compile on every request is the single most common performance mistake in hand-rolled Symfony-style apps.

Interop with Laravel: wrap the compiled Symfony container in a small PSR-11 adapter and register it as a Laravel singleton; or inverse — expose Laravel bindings to Symfony by writing a FactoryDefinition that calls app(). Do this at the edges (one bridge service provider), not inside domain services, or you get two containers fighting over lifecycle.

Gotchas and Tips

Private services vanish after compile. Since private: true is the default, $container->get(Foo::class) on a compiled container throws ServiceNotFoundException. Either mark the entry point public: true, alias it ($container->setAlias('public.foo', Foo::class)->setPublic(true)), or — better — keep the container a private implementation detail and only fetch the one or two roots you need (console app, HTTP kernel).

Compile-time vs runtime resolution is the #1 debugging trap. Parameters are resolved during compile; %env(VAR)% inside a service argument stays a runtime lookup. Editing a .env value will not invalidate the compiled container, but editing services.yaml will. Conversely, a parameter built from an env var cannot be used to decide which class to instantiate. When you see "You cannot use env vars in this position," that is the reason.

Circular references fail loudly at compile in modern versions (there have been several fixes in 7.3/8.1 for cycles through factories, expressions, and shared builders). Reach for lazy: true / #[Autowire(lazy: true)] or restructure — do not paper over it with setter injection. Note lazy proxies have had a string of fixes (inline definitions, decorated services, PHP 8.4 factories with leading backslash), so pin a recent patch release if you rely on them.

Autowiring ambiguity is silent until compile. Two services implementing the same interface and no alias = Cannot autowire service ... multiple candidates. Fix with an explicit alias: or #[Autowire(service: '...')] at the injection point. Run debug:autowiring (or lint:container in CI) to catch this before deploy, not in a smoke test.

_instanceof and attribute precedence bite. Class-level tags have historically overridden interface-level ones and vice versa depending on version; merging of explicit tags with #[AsTaggedItem] was fixed in 7.3.x. If tag ordering matters, assert it — do not assume the merge order.

Tagged iterator sorting and indexes: index_by, default_index_method, and default_priority_method behave differently across versions, and PriorityTaggedServiceTrait had fixes for non-list tag attributes. Test priority ordering explicitly; duplicates from autoconfiguration are a known historical bug.

ServiceLocator ids must be reproducible for caching. Dumped locator ids were made deterministic (8.1.x) — if you cache containers across machines or in CI artifacts, use a current patch release or you will get cache misses/thrash from anonymous service id drift.

Don't do work in a compiler pass that you could do at runtime. Passes run on every cold compile. Looping over filesystem scans, hitting the network, or reading the DB in a pass will make deploys feel broken. Keep passes to pure metadata manipulation.

ResourceTracking / staleness: if you use ContainerBuilder with setResourceTracking(true) and file resources, the container regenerates when config changes. Without it (or without wiring it into your cache key), you will ship a stale container — check whether your framework's cache warmer is doing this for you.

Factories and return types: autowiring a factory-produced service needs the class known. Set class: explicitly on the definition when the factory's return type is not the id, otherwise CheckTypeDeclarationsPass and dumper codegen can misbehave.

Error messages reference service_container and the concrete container class — factory resolution against it was fixed in 8.1.4. If you see odd service_container behavior with custom base classes or typed $parameterBag, you are hitting a version-specific bug; upgrade.

Testing: don't mock the container. Build a ContainerBuilder, load only the test-relevant config, compile(), and fetch the service under test. For framework apps, use the test container (test.service_container) which exposes private services. That is the equivalent of Laravel's $this->app->make() in a feature test — but it will surface real wiring errors instead of hiding them.

Extension points to remember: CompilerPassInterface, ExtensionInterface (for bundles), LoaderInterface (custom config formats), DumperInterface, and #[Autowire]/#[AutowireLocator]/#[AutowireCallable] attributes. Almost every need maps to one of these — resist the urge to inject a mutable registry.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource