Contextual Binding

Specific bindings for different contexts

graph TD A[PaymentGateway Interface] --> B[UserController Context] A --> C[AdminController Context] A --> D[ApiController Context] B --> E[StripePaymentGateway] C --> F[PayPalPaymentGateway] D --> G[SquarePaymentGateway] H[Service Container] -->|Contextual Binding| A

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