In our previous post(How to Add and Implement Payment Processing Interfaces in Laravel 11: Hardcoded Binding), we explored the first step in setting up payment processors by hardcoding the binding between the PaymentProcessorInterface and a specific implementation, like StripePaymentProcessor.
While this approach is simple and effective for small applications, it lacks flexibility for more complex scenarios where you might need to handle multiple payment gateways, but by using an interface, allowed us to decouple the code so we can further extend it, in accordance to open close principle, to inject the proper functionality:
- contextual binding at compile time, using Laravel Service Container mechanism.
- using factory pattern instantiating the required classes at runtime.
In this second part, we will dive into contextual binding, a more advanced technique in Laravel’s service container that allows you to inject different implementations of an interface based on the specific context. This is useful when the choice of a payment processor depends on the application state, such as which controller is handling the request.
Step 1: Understanding Contextual Binding
Contextual binding in Laravel allows the service container to inject different implementations of an interface depending on the class or context requesting it. Instead of relying on a single, hardcoded implementation, we can use contextual binding to resolve different payment processors based on the controller or some other contextual factor.
Step 2: Contextual Binding in the AppServiceProvider
Let’s start by configuring contextual bindings in the AppServiceProvider. We will bind different payment processors based on the controller that is requesting them. For example, the StripePaymentController will use StripePaymentProcessor, and the PayPalPaymentController will use PayPalPaymentProcessor.
Here’s how you can do it:
use App\Contracts\PaymentProcessorInterface;
use App\Services\StripePaymentProcessor;
use App\Services\PayPalPaymentProcessor;
public function register()
{
$this->app->when(StripePaymentController::class)
->needs(PaymentProcessorInterface::class)
->give(StripePaymentProcessor::class);
$this->app->when(PayPalPaymentController::class)
->needs(PaymentProcessorInterface::class)
->give(PayPalPaymentProcessor::class);
}
What's Happening Here?
- $this->app->when(): This tells Laravel to bind a particular implementation of an interface when a specific class (in this case, a controller) needs it.
- .needs(): This specifies that the class (StripePaymentController or PayPalPaymentController) needs an instance of PaymentProcessorInterface.
- .give(): This determines which concrete implementation to provide. For example, StripePaymentController gets StripePaymentProcessor, and PayPalPaymentController gets PayPalPaymentProcessor. This binding allows you to dynamically resolve the correct payment processor depending on which controller is handling the request.
Step 3: Separate Controllers for Each Payment Method
With contextual binding in place, each controller can now have its dedicated payment processor injected automatically. Here's how you can set up your controllers:
Example: StripePaymentController
use App\Contracts\PaymentProcessorInterface;
class StripePaymentController extends Controller
{
protected $paymentProcessor;
public function __construct(PaymentProcessorInterface $paymentProcessor)
{
$this->paymentProcessor = $paymentProcessor;
}
// Methods to handle Stripe-specific payments...
}
Example: PayPalPaymentController
use App\Contracts\PaymentProcessorInterface;
class PayPalPaymentController extends Controller
{
protected $paymentProcessor;
public function __construct(PaymentProcessorInterface $paymentProcessor)
{
$this->paymentProcessor = $paymentProcessor;
}
// Methods to handle PayPal-specific payments...
}
In both examples, Laravel automatically injects the correct payment processor based on the controller context. This is thanks to the contextual binding set up in the AppServiceProvider.
Why Use Contextual Binding?
Contextual binding is particularly useful when you know which implementation of an interface to use based on specific classes or contexts, such as controllers. It helps keep your code clean and manageable, especially when dealing with multiple payment gateways, each with its own controller.
Conclusion
In this post, we’ve explored how to implement contextual binding in Laravel 11 for payment processing. Here’s a quick recap of the benefits of this approach:
- Cleaner Code: No need for manual logic to choose between different payment processors.
- Automatic Injection: Laravel automatically injects the correct processor based on the context (controller).
- Flexibility: You can easily extend this approach to other parts of your application, such as different services or other contexts. When to Use Contextual Binding vs. the Factory Pattern
- Contextual Binding: Ideal when the processor can be selected based on specific classes (like different controllers) or known contexts. It simplifies the code where the context is known at compile time.
- Factory Pattern: Use the Factory Pattern if you want to dynamically select a payment processor based on runtime data (e.g., user input, API request). This approach offers more flexibility for selecting the payment processor, at runtime, based on data that may not be known until the request is processed.
In the next post, we will explore the Factory Pattern, which allows for dynamic selection of payment processors at runtime, providing even more flexibility for complex applications.
Stay tuned for the next part, where we’ll cover how to use factories for payment processing in Laravel 11!
Top comments (0)