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

Laravel Enhanced Container Laravel Package

michael-rubel/laravel-enhanced-container

A lightweight extension for Laravel’s service container, offering convenient helpers for binding, resolving, and managing dependencies. Simplify contextual bindings, singletons, and instance handling while keeping service providers clean, expressive, and easier to test.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the container swap. The package replaces Laravel's default container with an enhanced one via package discovery, so there's usually nothing to register manually:

composer require michael-rubel/laravel-enhanced-container

Publish the config (if you need to tweak defaults) and clear caches:

php artisan vendor:publish --tag=enhanced-container-config
php artisan config:clear

Confirm the enhanced container is active before assuming attribute behavior — a quick app()->make(SomeInterface::class) on an unbound interface should now resolve via attributes or fail with a container exception that references the enhanced binding logic.

On 13.0.3 specifically: it's a maintenance patch (dependency bumps plus PHP 8.4 deprecation fixes) — no new attributes, no changed resolution semantics, no migration steps. Pull it with a normal composer update michael-rubel/laravel-enhanced-container and clear config/autoload caches. The only reason to treat it as more than routine is if you run PHP 8.4: this is the release that quiets the deprecation notices, so upgrade before blaming the package for noisy logs.

Where to look first.

  1. config/enhanced-container.php — feature toggles (attribute binding, autobinding conventions, contextual defaults).
  2. src/Core/Attributes/ — the attribute classes (Bind, Singleton, BindWhen, etc.) are the public API surface you'll use daily.
  3. src/Core/Container.php — the overridden resolve/bind behavior, useful when debugging "why did this resolve to X?".
  4. The README examples — they're short and map almost 1:1 to the attributes you'll actually use.

First use case — delete boilerplate from AppServiceProvider. If AppServiceProvider::register() is a wall of $this->app->bind(FooInterface::class, Foo::class), move each binding to the contract itself:

use MichaelRubel\EnhancedContainer\Core\Attributes\Bind;

#[Bind(UserRepository::class)]
interface UserRepositoryInterface {}

Now app(UserRepositoryInterface::class), constructor injection in controllers, jobs, and listeners all work without a provider entry. That's the 80% use case and the first thing to migrate.

Implementation Patterns

Attribute binding on contracts and abstract classes. Put #[Bind] on your own interfaces/abstract classes. It keeps the mapping next to the contract, so refactors are local:

#[Bind(StripeGateway::class)]
interface PaymentGateway {}

#[Bind(CachedUserRepository::class)]
abstract class UserRepository {}

Explicit bindings registered in a service provider still take precedence — use the attribute as the default, and provider bindings for environment-specific overrides (staging mocks, feature flags).

#[Singleton] for stateless services. Mark classes that are expensive to construct and safe to share (config readers, clients, formatters):

#[Singleton]
class ExchangeRateClient { /* ... */ }

This is the attribute equivalent of $this->app->singleton(...), and it composes with #[Bind]: bind the contract to an implementation, and mark the implementation #[Singleton].

Contextual binding with attributes. Two flavors are useful day-to-day:

On the interface — declare "when class X asks for me, give it Y":

#[Bind(PayPalGateway::class)]
#[BindWhen(RefundController::class, SandboxGateway::class)]
interface PaymentGateway {}

On a constructor parameter — the most surgical override, ideal for one-off consumers that need a different implementation without leaking the mapping into the interface:

class ReportGenerator
{
    public function __construct(
        #[Bind(LocalDiskStorage::class)]
        private Storage $storage,
    ) {}
}

Parameter attributes only apply to constructor/method injection through the container. A bare app(ReportGenerator::class) resolves it fine, but a manually new ReportGenerator($storage) obviously bypasses the container entirely.

Proxy for deferred resolution and swappable services. proxy() returns a lightweight stand-in that resolves the real instance on first method call. This is the feature that changes how you write code, not just how you wire it:

use function MichaelRubel\EnhancedContainer\Core\proxy;

proxy(PaymentGateway::class)->charge($order);

// Swap the underlying instance at runtime (tests, feature flags, A/B):
proxy(PaymentGateway::class)->swap(new FakePaymentGateway);

Use it for:

  • Test doubles — swap once in setUp(), no $this->app->instance() plumbing.
  • Feature-flagged implementations — pick the concrete class per tenant/request without rebinding mid-flight.
  • Lazy resolution in hot paths — defer container work until the service is actually needed.

Integration points. Anywhere Laravel does container resolution, the package is transparent: controller method injection, job handle() signatures, Artisan command constructors, event listeners, middleware, and app()/resolve() calls. If a class is being resolved through the container, attributes apply. That means you can retire a lot of AppServiceProvider noise and keep the binding next to the contract it binds.

Testing workflow. The pattern that pays off fastest:

protected function setUp(): void
{
    parent::setUp();
    proxy(PaymentGateway::class)->swap(new FakePaymentGateway);
}

Because the swap applies to the shared container, every subsequent resolution (controllers, jobs, nested services) sees the fake — no per-test rebinding. Reset or re-swap in tearDown() if the same process resolves real implementations later.

Decorator / chain composition. Bind an interface to a decorator with #[Bind], then use standard extend() in a provider to wrap it further. The attribute handles the "default", extend handles the "and also" — keep those concerns separate so the attribute stays readable.

Gotchas and Tips

Explicit provider bindings beat attributes. If you're debugging "the attribute is ignored," check AppServiceProvider and any package provider first. $this->app->bind() / ->singleton() wins over attribute metadata. Same for ->instance() — a manually registered instance shadows everything.

#[Singleton] plus stateful services is an Octane/Swoole landmine. Under Octane, singletons persist across requests. A singleton that caches per-request data will leak users' data between requests. Rule of thumb: if it holds request-scoped state, don't mark it #[Singleton].

Proxies are not instances. proxy(Foo::class) instanceof Foo is false, and static analysis/IDEs won't see forwarded methods. Add @method docblocks or @mixin on your own proxy-typed properties, and don't type-hint against a proxy in strict signatures. Proxies forward via __call, so property access on the proxy won't reach the underlying object.

Attributes only work on classes you control. You can't put #[Bind] on a vendor interface. Wrap it in your own contract, or fall back to a provider binding for third-party packages.

Contextual attributes require container-driven construction. Constructor injection through the container honors #[Bind] on parameters; new, factories, and resolve() calls that bypass constructor resolution won't. If a contextual override "isn't applied," verify the consumer itself is being resolved by the container.

Resolution order and precedence. In practice: registered instance → registered binding → contextual binding → attribute binding → autowiring. When something resolves unexpectedly, dump app()->getBindings() and app()->getContextualBindings() (or inspect the enhanced container's internal maps) to see which layer supplied the answer.

Reflection cost is real but small. Attribute inspection happens at resolution time. In a hot loop, prefer resolving once and reusing, or use #[Singleton] for the service being resolved repeatedly. If you're seeing container overhead in profiles, that's usually the cause, not the attribute lookup itself.

PHP 8.4 deprecations — fixed in 13.0.3. If you're on PHP 8.4 and pinning an older 13.0.x, expect deprecation notices surfacing from the package during resolution. They're harmless but they pollute logs and will trip test suites running with failOnDeprecation/strict error handlers. Upgrade rather than suppress; this patch is the fix. Nothing in your own code needs changing, and no attribute API or config key moved.

Config caching and package updates. After changing anything in config/enhanced-container.php or upgrading (13.0.3 is a dependency/deprecation maintenance release — no breaking changes), run php artisan config:clear and composer dump-autoload. Stale config caches are the most common "the feature isn't on" report. Lockfile drift on the bumped dependencies is the other: if composer update pulls a new illuminate/* minor alongside the patch, re-run your container-resolution tests before shipping.

Container replacement conflicts. Some packages override or wrap the container themselves, or assume Illuminate\Container\Container is the concrete class. If you see odd behavior after installing or upgrading, temporarily disable the package's container swap in config and see if the symptom disappears — that isolates whether the enhanced container is the culprit. Do this before opening an issue against the patch release.

Debugging swaps. If swap() appears to have no effect, check when it's called relative to the first resolution. A singleton already resolved before the swap keeps the old instance; only subsequent resolutions (or proxy calls that resolve after the swap) see the new one. For deterministic tests, swap in setUp() before anything touches the service.

Extension points worth knowing. The attributes are ordinary classes — you can subclass or write your own that implement the same interface the container checks for, if you need a custom resolution rule (e.g., "bind by config value"). Likewise, the enhanced container is extensible; if you find yourself writing the same contextual mapping in ten places, that's the signal to introduce a dedicated attribute rather than repeating #[BindWhen].

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