Dynamic Service Container Tagging for Runtime Logic Switching
What is it? Did you know that Laravel’s Service Container supports tagging for grouping multiple implementations of an interface or class? While tagging itself is documented, here’s the twist:
You can use dynamic tagging and runtime resolution to select implementations on the fly, making your services incredibly flexible without hardcoding anything.
The Hack
- Define a Base Interface
Let’s say you have an interface for payment gateways:
1namespace App\Contracts;2 3interface PaymentGateway4{5 public function process($amount);6}
- Create Multiple Implementations
Define a couple of implementations for this interface:
1namespace App\Services\Payments; 2 3use App\Contracts\PaymentGateway; 4 5class StripeGateway implements PaymentGateway 6{ 7 public function process($amount) 8 { 9 return "Processing {$amount} via Stripe!";10 }11}12 13class PayPalGateway implements PaymentGateway14{15 public function process($amount)16 {17 return "Processing {$amount} via PayPal!";18 }19}
- Tag Services in a Service Provider
In your AppServiceProvider
, tag these implementations dynamically:
1use App\Contracts\PaymentGateway; 2use App\Services\Payments\StripeGateway; 3use App\Services\Payments\PayPalGateway; 4 5public function register() 6{ 7 $this->app->bind(StripeGateway::class, fn () => new StripeGateway()); 8 $this->app->bind(PayPalGateway::class, fn () => new PayPalGateway()); 9 10 // Dynamically tag both services11 $this->app->tag([StripeGateway::class, PayPalGateway::class], 'payment_gateways');12}
- Resolve Dynamically Based on Logic
Now you can resolve and switch between implementations at runtime based on dynamic conditions:
1use Illuminate\Contracts\Container\Container; 2 3function getPaymentGateway(Container $container, $preferredGateway): PaymentGateway 4{ 5 $gateways = $container->tagged('payment_gateways'); 6 7 foreach ($gateways as $gateway) { 8 if (get_class($gateway) === $preferredGateway) { 9 return $gateway;10 }11 }12 13 throw new \Exception("No gateway found for {$preferredGateway}");14}15 16// Example usage17$gatewayClass = \App\Services\Payments\StripeGateway::class; // Could come from config or runtime logic18 19$gateway = getPaymentGateway(app(), $gatewayClass);20 21echo $gateway->process(100); // Outputs: Processing 100 via Stripe!
- Bonus Hack: Auto-Tag Based on Folder Scanning
Let’s take it a step further and auto-tag classes in a folder:
1use Illuminate\Support\Facades\File; 2 3public function register() 4{ 5 $paymentServices = collect(File::allFiles(app_path('Services/Payments'))) 6 ->map(fn ($file) => 'App\\Services\\Payments\\' . $file->getFilenameWithoutExtension()) 7 ->filter(fn ($class) => class_exists($class)) 8 ->each(fn ($class) => $this->app->bind($class, fn () => new $class())); 9 10 $this->app->tag($paymentServices->all(), 'payment_gateways');11}
Now, any new class in App\Services\Payments
is automatically tagged and ready to use.