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

Di Extra Bundle Laravel Package

rickysu/di-extra-bundle

A Symfony bundle that adds annotation-based dependency injection configuration, letting you declare services, parameters, and injections directly in PHP classes instead of XML or YAML files.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with a scoping decision, not an installation. rickysu/di-extra-bundle is a Symfony 4 bundle. It registers through Kernel::registerBundles(), contributes container compiler passes, and pins symfony/* to ^4.x. A Laravel application has no bundle loader, no compiler passes, and already ships Symfony 6/7 components — composer require will fail resolution, and forcing it with --ignore-platform-reqs corrupts your framework's dependency tree. Treat the package as a reference architecture, not a dependency.

Where to look first (in this order, ~2 hours):

  1. DependencyInjection/RSDiExtraExtension.php — the bundle's entry point. This shows what configuration knobs exist and what defaults it sets.
  2. DependencyInjection/CompilerPass/* — the actual mechanic. This is where "annotation-driven DI" lives; read these passes to understand how class annotations become Definition objects.
  3. Resources/config/ — service definitions for the bundle's own plumbing (annotation readers, the registry that collects scanned classes).
  4. composer.json — confirm the hard constraints (symfony/* version, PHP floor). This is your go/no-go signal.

First use case — your first useful use case is a mapping exercise, not an integration. Pick three or four of your own service providers that feel boilerplate-heavy (repositories with interface bindings, decorators, tagged collections). For each, ask: what would this look like if I could declare it on the class itself? Then check whether Laravel already answers it:

You want… Laravel already gives you
Bind an interface to a concrete class $this->app->bind(Interface::class, Impl::class)
Contextual resolution (different impl per consumer) $this->app->when(CsvExporter::class)->needs(Formatter::class)->give(RowFormatter::class)
Decorate an existing service $this->app->extend(Client::class, fn ($c, $app) => new RetryClient($c))
Collect services by capability $this->app->tag([A::class, B::class], 'exporters') then tagged('exporters')
Attribute-driven wiring (L11+) #[ContextualAttribute], #[Singleton], #[Bind]
Auto-discovery without touching config/app.php composer.json → extra.laravel.providers

If a spike is warranted at all, run it in an isolated container with PHP 7.4 + Symfony 4.4, never in an application composer.json. Treat the output as an internal note, not a PR.

The one legitimate trigger for a real read: your organisation actually operates a frozen Symfony 4 service that already uses @Service / @Inject / @Tag annotations (typically a half-finished JMSDiExtraBundle migration). In that narrow case you're evaluating a stopgap, and the right owner is whoever owns that service's retirement date — not the Laravel team.

Implementation Patterns

These are the patterns the bundle implements, and the Laravel idiom each one maps to. Use them as a design vocabulary when you write or refactor service providers.

1. Annotation-as-definition. The bundle's core move: scan classes for @Service, synthesise a Definition, register it under a guessed ID (usually the FQCN). Laravel's equivalent is a base provider that reads the same class-level metadata:

abstract class AttributeAwareProvider extends ServiceProvider
{
    public function register(): void
    {
        foreach ((new ClassScanner(base_path('app/Services')))->classes() as $class) {
            $ref = new ReflectionClass($class);
            $attr = $ref->getAttributes(Singleton::class)[0] ?? null;
            if ($attr) {
                $this->app->singleton($class);
            }
        }
    }
}

Reach for this only when you have genuine repeated boilerplate. Attribute scanning is invisible in a stack trace and easy to over-apply. Prefer explicit provider methods unless you have ~20+ services to wire.

2. Constructor injection as the default, property injection as the exception. The bundle supports @Inject on properties, which is the annotation-era equivalent of app()->make() inside a method — convenient, untestable. In Laravel, constructor injection through the container is already the idiom; keep it. If you're mirroring property injection for a legacy-compatible shim, mark the class final and the property private so the shortcut doesn't spread.

3. Tag-by-annotation → explicit tagging. @Tag("kernel.event_listener") becomes $this->app->tag([ListenerA::class, ListenerB::class], 'listeners'). The value here is that annotation tagging is discoverable at the class — you can see what a class participates in without reading config/. If that discoverability is the feature you actually want, add a #[Tag]-style attribute to your own codebase and resolve tagged() in the provider. That's ~40 lines and has no Symfony dependency.

4. Decoration ordering. The bundle's compiler passes register decorators during container compilation, which means ordering is determined by pass priority. In Laravel, decoration is explicit and ordered by call sequence:

$this->app->extend(Publisher::class, fn ($p) => new LoggingPublisher($p));
$this->app->extend(Publisher::class, fn ($p) => new RetryPublisher($p));

The Laravel form is strictly better for debugging: the order is the code you read. Do not try to replicate priority-based decoration ordering.

5. Auto-registration of a namespace. The bundle's "automatic service registration/discovery" is the closest analogue to Laravel's extra.laravel.providers or a directory-scanning provider. If you build the internal version, gate it behind a config flag so a new team member can find every registration in one place:

// config/services.php
return [
    'autoscan' => [
        'app/Listeners' => 'singleton',
        'app/Actions'   => 'bind',
    ],
];

6. The workflow you actually adopt. Concretely: when you find yourself writing the same four lines of bind() / tag() / extend() across providers, extract a base provider or a small attribute layer — then delete the extracted abstraction the moment it stops paying for itself. The bundle exists because that trade-off was made once, in 2018, and never revisited. Don't repeat it.

Gotchas and Tips

You cannot install this into Laravel — by any configuration. The blockers are independent and both fatal: (a) there is no bundle registration path in Illuminate\Foundation\Application, and (b) symfony/*: ^4.x collides with the symfony/*: ^6.0|^7.0 components that Laravel 10/11 ship. --ignore-platform-reqs and replace only move the failure from composer install to runtime class not found.

Symfony 4.4 reached end-of-life in November 2022. If you ever do run this for real, you're running it on an unpatched runtime. That's a compliance and audit finding, not just a technical one. Whatever you build must have a removal date written down at introduction.

PHP 8 will likely break the source. Symfony-4-era bundles frequently call ReflectionParameter::getClass(), removed in PHP 8.0, and rely on container compilation APIs that were tightened in 5.x. Expect to patch DependencyInjection/CompilerPass/* if you try to run it on 8.2/8.3. Budget this as fork maintenance, not configuration.

Adoption signals are the whole story: 7 stars, 0 dependents, 1 maintainer. Zero dependents means no third party has validated the code against real workloads, no bug reports exist to read, and there is no searchable corpus when a container compile fails. If you fork, you are the upstream. MIT is the one clean positive — fork-and-patch is legally unencumbered.

Failure modes to recognise when debugging anything you build in this shape:

  • Silent — wrong service resolved. Autowiring picks a plausible-but-wrong concrete class. Tests pass; a specific production code path fails. Mitigation: bind interfaces explicitly rather than relying on convention, and add a test that asserts app(Interface::class) is the expected class.
  • Silent — decoration ordering. Priority-based or pass-order-based decoration is nondeterministic relative to other registrations. Symptom: intermittent double-logging or a retry wrapper that runs inside the logger. Mitigation: explicit extend() chains, ordered top-to-bottom.
  • Hard — container compile failure after a PHP minor upgrade. Deprecation-as-error under strict settings, or a removed reflection API. Blocks deploy, not just the feature.
  • Hard — CI resolution failure. If a Symfony 4 pin ever lands in a Laravel composer.json, every build breaks, not just the one feature.

Config quirks worth noting if you read the source. The bundle's extension typically exposes annotation vs xml/yaml discovery modes and a list of scanned directories. These are not portable concepts — Laravel's equivalent is your own config/services.php. Don't try to mirror Symfony's extension/configuration-tree idiom (Configuration.php + TreeBuilder) into Laravel; it's more ceremony than the problem deserves.

Extension points, ranked by cost/benefit for a Laravel team:

Option Effort Verdict
Read the compiler passes, write an ADR 0.5 day Do this. Cheap, prevents the question resurfacing.
Build an internal ~150-line attribute-scanning base provider 2–3 days Worth it only if you have 20+ repetitive bindings and a code-review culture that will police it.
Vendor/fork the bundle for a legacy Symfony 4 service 2–5 days + ongoing Only if the service is already frozen and has a retirement date. File as technical debt.
Adopt as a dependency in a Laravel app — Impossible. Category mismatch.

The one habit worth stealing. The bundle's real insight is that service participation should be visible at the class, not buried in YAML three directories away. That's the same reason Laravel 11 added #[Singleton], #[Bind], and #[ContextualAttribute]. If your providers are getting verbose, the fix is those attributes — not a 7-star Symfony 4 bundle. The bundle is a fossil of a good idea the framework has since shipped natively; use the idea, skip the fossil.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource