Contextual Binding
Specific bindings for different contexts
Contextual Binding allows you to bind different implementations for specific classes. This is Laravel's powerful feature for managing context-aware dependencies that lets you map an interface to different implementations in different contexts.
Example:
Suppose you have a PaymentGateway interface. You want it to be StripePaymentGateway when used in UserController, but PayPalPaymentGateway in AdminController.
Benefits of Contextual Binding:
- High flexibility in dependency management
- Ability to use different implementations in different contexts
- Reduced coupling and increased testability
- Ability to create strategy pattern using binding
- Better management of business logic in different layers
When to Use:
- When an interface needs different implementations in different contexts
- To create strategy pattern
- For tests that need different mocks
- For separation of concerns in different application layers
Examples
Contextual Binding Example
app()->when(UserController::class)
->needs(PaymentGateway::class)
->give(StripePaymentGateway::class);
app()->when(AdminController::class)
->needs(PaymentGateway::class)
->give(PayPalPaymentGateway::class);
For UserController, StripePaymentGateway is used, and for AdminController, PayPalPaymentGateway is used.
Contextual Binding with Closure
app()->when(OrderService::class)
->needs(NotificationService::class)
->give(function ($app) {
return new EmailNotificationService(
$app->make('config')
);
});
app()->when(AdminService::class)
->needs(NotificationService::class)
->give(function ($app) {
return new SlackNotificationService(
$app->make('config')
);
});
You can use closure to create instance with dependency resolution.
Contextual Binding for Multiple Interfaces
app()->when(ReportGenerator::class)
->needs(StorageInterface::class)
->give(S3Storage::class);
app()->when(ReportGenerator::class)
->needs(LoggerInterface::class)
->give(FileLogger::class);
class ReportGenerator
{
public function __construct(
private StorageInterface $storage,
private LoggerInterface $logger
) {}
}
You can contextually bind multiple interfaces for one class.
Contextual Binding in Service Provider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class PaymentServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(UserController::class)
->needs(PaymentGateway::class)
->give(StripePaymentGateway::class);
$this->app->when(AdminController::class)
->needs(PaymentGateway::class)
->give(PayPalPaymentGateway::class);
}
}
Best practice is to perform contextual bindings in Service Provider.
Contextual Binding with Primitives
app()->when(EmailService::class)
->needs('$apiKey')
->give(config('services.mailgun.key'));
app()->when(SmsService::class)
->needs('$apiKey')
->give(config('services.twilio.key'));
class EmailService
{
public function __construct(private string $apiKey) {}
}
You can also contextually bind primitive values.
Contextual Binding for Testing
// In TestCase setUp
protected function setUp(): void
{
parent::setUp();
$this->app->when(OrderService::class)
->needs(PaymentGateway::class)
->give(MockPaymentGateway::class);
}
// In specific test
public function test_order_processing()
{
$service = app(OrderService::class);
// Uses MockPaymentGateway
}
In tests you can use contextual binding for mocking.
Contextual Binding with Tagged Services
app()->when(ReportAggregator::class)
->needs('$reportGenerators')
->giveTagged('report.generators');
app()->bind('report.generators', function ($app) {
return [
$app->make(SalesReportGenerator::class),
$app->make(InventoryReportGenerator::class),
];
});
You can use tagged services for contextual binding.
Use Cases
- Using different payment gateways in different controllers
- Using different notification services (email, SMS, Slack) based on context
- Using different storage drivers (local, S3, Azure) in different services
- Using different loggers for different application layers
- Using different cache drivers based on data type
- Creating strategy pattern using contextual binding
Common Mistakes
- Using contextual binding for everything instead of using strategy pattern
- Forgetting to bind default causing errors
- Using contextual binding at runtime which has performance overhead
- Not using contextual binding in tests causing isolation issues
- Using concrete classes instead of interface in contextual binding
- Not documenting contextual bindings causing confusion
Best Practices
- Always perform contextual bindings in Service Provider
- Use interface, not concrete classes
- Define a default binding as well for cases where context is not clear
- Use contextual binding for separation of concerns
- Use contextual binding for mocking in tests
- Document which implementation is used in which context
Edge Cases
- Contextual binding in queue jobs which have different container instance
- Contextual binding in console commands which may have different context
- Overriding contextual binding at runtime which may cause issues
- Contextual binding for nested dependencies requiring resolution chain
- Using contextual binding in event listeners which may be resolved at different times
Performance Notes
- Contextual binding in Service Provider is faster than at runtime
- Excessive use of contextual binding can slow down container resolution
- Use deferred providers for rarely used contextual bindings
- Contextual binding with type-hinting is faster than string-based
Security Notes
- Ensure sensitive contextual bindings (like API keys) are configured in appropriate environment
- Use contextual binding for separation of concerns in security layers
- Ensure admin contexts use appropriate security bindings
Interview Points
- What is the difference between contextual binding and regular binding?
- When should you use contextual binding?
- How can you define a default binding for contextual binding?
- Can you define multiple contextual bindings for one class?
- How can you use contextual binding in tests?
- What are the benefits of using contextual binding in architecture?
Version Notes
- Laravel 11.x: Improved performance in contextual binding resolution
- Laravel 10.x: Better support for tagged services in contextual binding
- Laravel 9.x: Added giveTagged() method for contextual binding