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 Extra Laravel Package

chilldev/dependency-injection-extra

Extra utilities and extensions for the chilldev dependency injection container, adding convenient helpers, configuration options, and integrations that simplify service wiring and make complex dependency graphs easier to manage in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is not a Laravel package. chilldev/dependency-injection-extra is a Symfony 2.x DI extension ("commonly used patterns for Symfony2 DI provided out of the box"). There is no service provider, no facade, no config/*.php to publish, and no illuminate/* dependency anywhere in its tree. Its composer.json pins Symfony 2.x components, which are hard-conflicting with the symfony/* ^6/^7 that modern Laravel pulls in. You cannot composer require it into a Laravel app and get anything wired up — at best the autoloader picks up a few classes that nothing calls.

So "getting started" splits into two paths:

  1. You are in a Laravel codebase → do not install it. Skip to Implementation Patterns, which shows how to reproduce each of its patterns natively. The package's whole value proposition is ~2–3 small classes; reimplementing them is cheaper than an adapter.
  2. You are maintaining a legacy Symfony 2 app (or porting one to Laravel) → this is a reference/pattern source, not a daily-use dependency. Read it, extract the compiler-pass logic, then drop the requirement.

Minimal steps if you are inspecting it:

git clone https://github.com/chilloutdevelopment/ChillDevDependencyInjectionExtra
cd ChillDevDependencyInjectionExtra
cat composer.json          # confirm the Symfony version pin — this dates the codebase
ls DependencyInjection/    # the entire useful surface lives here
ls DependencyInjection/Compiler/

Where to look first:

  • composer.json — version constraints tell you instantly whether it can coexist with anything you run today (PHP 8.1+? Likely not).
  • DependencyInjection/Compiler/ — the compiler passes. This is the part with actual ideas in it: collecting tagged services into a registry, ordering them, injecting them without hand-written YAML per consumer.
  • DependencyInjection/ root — any factory/helper for building services with runtime arguments that DI config can't express.
  • README.md / Resources/doc — the author's intended usage snippets; usually a services.yml example plus a pass registration in the bundle class.

First use case you'll actually hit — collecting tagged services into a registry, the "chain of handlers / list of reporters / set of voters" pattern:

# Symfony 2.x services.yml
services:
    app.report.registry:
        class: AppBundle\Report\Registry
        arguments: [ [] ]

    app.report.alpha:
        class: AppBundle\Report\AlphaReport
        tags:
            - { name: app.report, priority: 10 }

    app.report.beta:
        class: AppBundle\Report\BetaReport
        tags:
            - { name: app.report, priority: -5 }

Symfony 2.x has ContainerBuilder::findTaggedServiceIds() but no priority sorting — PriorityTaggedServiceTrait only lands in Symfony 3.3. That gap is precisely what this package existed to fill. If the helper class isn't there or is broken on your PHP version, the twenty-line replacement is:

private function findAndSortTaggedServices(string $tag, ContainerBuilder $container): array
{
    $found = [];
    foreach ($container->findTaggedServiceIds($tag) as $id => $attrs) {
        foreach ($attrs as $attr) {
            $found[] = [$attr['priority'] ?? 0, $id];
        }
    }

    usort($found, fn ($a, $b) => $b[0] <=> $a[0]); // desc, stable-ish

    return array_map(
        fn ($pair) => new Reference($pair[1]),
        $found
    );
}

For the Laravel equivalent, see below — it's five lines of tag().

Implementation Patterns

Pattern 1: Tagged service registry with ordering

Symfony (what the package gives you). Register the pass in the bundle's build():

class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new ReportPass());
    }
}

class ReportPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('app.report.registry')) {
            return;
        }

        $registry = $container->getDefinition('app.report.registry');
        foreach ($this->findAndSortTaggedServices('app.report', $container) as $ref) {
            $registry->addMethodCall('add', [$ref]);
        }
    }
}

Note the pass mutates the definition, not a live object — this all resolves at container compile time, and the sorted list is baked into the compiled container.

Laravel (what you should write instead). Laravel has tag()/tagged() since 5.x, but tags carry no attributes, so priority has to live somewhere else — config is the least surprising place:

// config/reports.php
return [
    'priorities' => [
        \App\Reports\AlphaReport::class => 10,
        \App\Reports\BetaReport::class => -5,
    ],
];
// app/Providers/ReportServiceProvider.php
public function register(): void
{
    $this->app->tag([
        \App\Reports\AlphaReport::class,
        \App\Reports\BetaReport::class,
    ], 'app.reports');

    $this->app->singleton(ReportRegistry::class, function ($app) {
        $priorities = $app['config']->get('reports.priorities', []);

        $registry = new ReportRegistry();

        foreach (collect($app->tagged('app.reports'))
            ->sortByDesc(fn ($report) => $priorities[$report::class] ?? 0)
            ->values() as $report) {
            $registry->add($report);
        }

        return $registry;
    });
}

Resolve the registry lazily (singleton closure, not boot()), so a tagged report that itself type-hints ReportRegistry doesn't blow up at boot. If a report genuinely needs the registry, inject Container or a ReportRegistry proxy rather than the eager singleton.

Pattern 2: Factory for runtime constructor arguments

The other thing this class of package offers is building a service from inside container-aware code when the constructor args aren't known at config time (a request-scoped client, a per-tenant connection). Symfony 2.x had no autowiring worth the name, so the factory indirection was load-bearing.

In Laravel you almost never need it — use a closure binding with runtime state passed explicitly:

$this->app->bind(TenantClient::class, fn ($app) => new TenantClient(
    $app->make(ConnectionFactory::class),
    $app->make(TenantResolver::class)->current()
));

Prefer this over App::make(Foo::class, ['host' => ...]); the parameter-override form is undocumented-ish and silently falls back to container resolution for anything you typo. If you're porting a Symfony factory service, the faithful translation is a named, injectable factory class (TenantClientFactory), not a container call sprinkled through business logic.

Pattern 3: Decoration / aliasing

The package-style "replace this service with a decorated one" pass maps to Laravel's extend():

$this->app->extend(ReportRegistry::class, function (ReportRegistry $inner, $app) {
    return new AuditedReportRegistry($inner, $app->make(LoggerInterface::class));
});

extend() returns a new binding for the same abstract and keeps the original as $inner. Order matters: extend() after the original binding, which is why it belongs in boot() of the decorating provider, not register().

Pattern 4: Contextual arguments

Symfony's per-service argument overriding has a direct Laravel analogue — reach for it before writing a factory:

$this->app->when(AlphaReport::class)
    ->needs(Formatter::class)
    ->give(fn () => new JsonFormatter(pretty: true));

Workflow integration tips when porting

  • Keep the tag name in one place. In Symfony it's a string duplicated across services.yml and the pass; in Laravel duplicate the 'app.reports' literal between provider and collector and you get an empty registry with no error. Put it in a const.
  • The compiler-pass logic is compile-time in Symfony and runtime in Laravel. Anything you port must be safe to run on every request — cache the sorted result in a singleton, don't re-sort per call.
  • Keep register() for bindings and tags, boot() for extend() and anything needing other providers fully registered.
  • If you are extracting from the Symfony repo to port: take DependencyInjection/Compiler/* only. The Bundle/Extension classes (AppBundle, Configuration, AppExtension) have zero meaning in Laravel and will only confuse the diff.

Gotchas and Tips

Adoption risk is the headline. Zero stars, zero dependents, a Symfony 2.x era constraint, no PHP 8 support, no releases you can trust for security patches. Treat it as a readable artifact, not a dependency. If you're on a legacy Symfony 2 app that already uses it, pin the exact commit you have and plan to remove it — the replacement is smaller than the package.

findAndSortTaggedServices is not stable across Symfony versions. Symfony 3.3+ ships PriorityTaggedServiceTrait::findAndSortTaggedServices() on ContainerBuilder subclasses with slightly different semantics (it filters, accepts IndexedIterator later, etc.). If you're upgrading a legacy app, delete the package's helper and use the framework's — otherwise you'll have two sorters with two tie-breaking behaviours.

Priority ties are non-deterministic. usort is not stable pre-PHP 8.0 and unstable-by-design in PHP 8+. With two handlers both at priority: 0, their relative order can change between deploys. Always tie-break on service id / class name so the chain is reproducible. This bites hardest in Laravel, where tagged() returns in provider registration order — reordering config/app.php providers silently reorders your handlers.

Laravel tagged() returns an empty array, silently, when a class is bound but not tagged. No exception, no warning. First debugging step: php artisan tinker → app()->tagged('app.reports'). If empty, check that the tag() call is in a provider that actually loaded (a --provider scoped php artisan command, or a package provider not in bootstrap/providers.php, will not have run it).

Private services (Symfony 2.8+). Compiler passes that getDefinition('x') on a now-private service throw or return a definition you can't touch. This package predates that shift, so its passes were written against a mostly-public container — another reason the code doesn't survive forward into Symfony 3/4, let alone Laravel.

Config caching hides ordering bugs. Laravel caches the container's singletons across a request but rebuilds the app each time; config:cache freezes config/reports.php. If your priorities are computed at runtime (feature flags, DB), do not put them under config:cache — read them in the singleton closure from the DB/cache layer instead, or php artisan optimize:clear will look like a fix.

Debugging the container. Symfony: bin/console debug:container --tag=app.report. Laravel: app()->getBindings() (array of closures, not resolved instances), app()->resolved(Foo::class), and php artisan about for a coarse view. Neither gives you a nice tag tree — a php artisan make:command that dumps app()->tagged() is worth the ten minutes if you have more than three tags.

Extension points, if you insist on reusing it. Don't. But if you must, vendor the compiler pass into app/Support/Symfony/ and adapt the method calls (addMethodCall('add', [$ref]) → collecting into a closure-built registry). Writing a shim that registers a Symfony ContainerBuilder beside illuminate/container produces two containers, two lifecycles, and a class of bugs — duplicate singletons — that no amount of dd() will untangle cleanly.

The one idea worth keeping. The registry-with-priority pattern is genuinely good, and the package's real contribution was normalising "tag consumers, sort producers, inject the list" so no consumer knows its siblings by name. Implement that once as an app/Support/TaggedRegistry.php base class and every future plugin-ish feature (exporter chains, middleware-like stages, health checks, sitemap generators) becomes a tag and a config entry instead of a constructor edit.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator