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

Expander Bundle Laravel Package

sergeil/expander-bundle

Symfony bundle that integrates the Expander library into your application, registering services and configuration so you can expand nested values and resolve paths cleanly. It provides straightforward container wiring and defaults for reusable expansion logic across your project.

View on GitHub
Deep Wiki
Context7

Getting Started

The sergeil/expander-bundle is a Symfony bundle, not a Laravel package — it does not ship a ServiceProvider, facades, or anything Composer-autoloadable into a Laravel app. Before you write any code, treat it as two things: (1) a drop-in extension-point mechanism if you happen to be on Symfony, and (2) a reference implementation of the tagged-service pattern you may want to port into Laravel.

If you are actually on Symfony:

composer require sergeil/expander-bundle:^1.2

Version note (v1.2.1): this release added Symfony 5 compatibility. That makes v1.2.1 the first version you should consider if your app is on Symfony 5.x — but it also means the bundle's ceiling is Symfony 5. Pin symfony/framework-bundle accordingly and do not assume Symfony 6/7 support; there is no release note claiming it.

Register it in config/bundles.php:

return [
    Sergeil\ExpanderBundle\ExpanderBundle::class => ['all' => true],
];

Then look first at three things in this order:

  1. The extension point abstraction (the base class/interface you will subclass to declare "I collect tagged things").
  2. The compiler pass that reads the DIC tags and feeds them into your extension point. This is where ordering/priority lives.
  3. The tests in the repo — with only 2 stars and a handful of releases, tests are the de-facto documentation.

Your first real use case: replacing a hand-rolled match/switch factory. Instead of a PaymentGatewayFactory with a hardcoded match ($type) block, you declare one extension point, tag each gateway service, and inject the collected set.

For a Laravel developer, the closest first step is to read the bundle and then reproduce the idea with Laravel's own container tagging ($this->app->tag([...], 'payments.gateway') + $this->app->bind(GatewayRegistry::class, fn ($app) => new GatewayRegistry($app->tagged('payments.gateway')))). Same concept, no bundle required.

Implementation Patterns

1. Declare an extension point, then tag implementations. Define a single contract (e.g. GatewayInterface) and an extension point class that receives iterable $gateways. Tag every implementation:

# services.yaml
App\Payment\StripeGateway:
    tags: ['app.payment_gateway']

Autoconfiguration (Symfony 5.3+) is the cleaner pattern — put the tag on the interface so every implementation is picked up automatically:

#[AutoconfigureTag('app.payment_gateway')]
interface GatewayInterface {}

Because v1.2.1 targets Symfony 5, you are on the framework generation where attribute-based autoconfiguration and _defaults: autoconfigure: true are the intended path — use them, and drop the annotation-style equivalent unless you still support Symfony 4 code paths.

2. Keep ordering in the extension point, not the callers. If the bundle exposes priorities on the tag, use them; otherwise sort deterministically inside your extension point (by a supports() score or an explicit getPriority() on the interface). Never rely on container iteration order.

3. Use a service locator for expensive services. If your tagged services open connections or do heavy constructor work, inject ServiceLocator rather than an eager iterable, otherwise every container boot instantiates all tagged services. This is the single biggest production concern with tag-based registries.

4. Integration entry points. The extension point should be injected into:

  • Controllers / invokable actions (thin — resolve and delegate).
  • Console commands (php artisan-equivalent bin/console) for listing/selecting registered extensions.
  • Messenger/queue handlers where the message carries a key and the extension point dispatches.

5. Testing pattern. Assert against the compiled container rather than the class:

self::assertCount(3, self::getContainer()->get(GatewayRegistry::class)->all());

Then unit-test each tagged implementation in isolation. This keeps the extension point wiring separate from business logic.

6. Porting to Laravel (the realistic path). If your goal is "this pattern in a Laravel app":

// AppServiceProvider::register()
$this->app->singleton(GatewayRegistry::class, function ($app) {
    return new GatewayRegistry($app->tagged('payments.gateways'));
});

// AppServiceProvider::boot()
$this->app->tag([StripeGateway::class, PaypalGateway::class], 'payments.gateways');

Or lean on contextual binding if you only ever need one implementation per key:

$this->app->when(CheckoutController::class)
    ->needs(GatewayInterface::class)
    ->giveTagged('payments.gateways');

The Laravel pattern is strictly more ergonomic than the Symfony bundle for this job, which is worth noting in your evaluation.

Gotchas and Tips

  • Version ceiling is Symfony 5. v1.2.1 exists specifically to add Symfony 5 support; nothing in the changelog promises Symfony 6/7. If your app is mid-migration to a newer Symfony, you cannot lean on this bundle — vendor the compiler pass instead. Verify the constraint in the bundle's own composer.json before composer require, since a permissive-looking tag can still resolve against an incompatible framework-bundle.
  • Tags are compile-time. Anything registered dynamically at runtime (after the container is compiled) will not appear in the extension point. If you need runtime plugins, use a registry service, not DIC tags.
  • Clear the cache after tag changes. In Symfony, bin/console cache:clear — stale compiled containers are the #1 source of "my service isn't showing up." In Laravel, same class of bug with php artisan optimize:clear. This is doubly true after a bundle version bump: a container compiled against v1.0 will not pick up v1.2.1's behaviour until you clear.
  • Autoconfiguration must be on. Tag-on-interface only works if the service is defined under a _defaults: autoconfigure: true block. Manually defined services with autoconfigure: false silently miss the tag.
  • Debugging. Symfony: bin/console debug:container --tag=app.payment_gateway lists tagged services; bin/console lint:container catches wiring errors before runtime. There is no equivalent Laravel command — use php artisan tinker and inspect app()->tagged('...').
  • Bundle registration is mandatory and non-obvious. Forgetting config/bundles.php produces a "class not found" or silently does nothing, because the compiler pass never runs.
  • Ordering is not guaranteed. The container's tag iteration order is stable but not contractual. Explicitly sort in your extension point if order matters (e.g. validation pipelines).
  • Low maintenance risk. 2 stars, 0 dependents, releases are sparse and patch-numbered; v1.2.1 is a compatibility fix, not a feature release. Assume no upstream fixes beyond Symfony-5-era support. Vendor it by copying the compiler pass into your own bundle if you adopt it — the concept is ~100 lines and far safer to own than to depend on.
  • Naming drift. Tag names should be constants on your interfaces (GatewayInterface::TAG), not inline strings sprinkled across services.yaml and ServiceProvider files. This is the single highest-leverage tip for both Symfony and the Laravel port.
  • Extension points are not a substitute for a DI-agnostic plugin system. If you ever need third-party plugins loaded from arbitrary packages, tags won't cut it — you need a discovery mechanism plus a tagged bridge.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
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
cgv/localized-resource