Here is a detailed example of implementing the Strategy pattern in a Laravel application:
The Context
Let's say we have an e-commerce application that needs to handle different payment methods. We want to make it easy to add new payment methods in the future without modifying the existing code.
The Strategy Interface
First, we define a `PaymentStrategy` interface that declares the methods common to all payment strategies:
php
namespace App\Strategies;
interface PaymentStrategy
{
public function pay(float $amount): bool;
public function getPaymentMethod(): string;
}
Concrete Payment Strategies
Next, we create concrete implementations of the `PaymentStrategy` interface for each payment method:
php
namespace App\Strategies;
class StripePaymentStrategy implements PaymentStrategy
{
public function pay(float $amount): bool
{
// Implement Stripe payment logic
return true;
}
public function getPaymentMethod(): string
{
return 'Stripe';
}
}
php
namespace App\Strategies;
class PaypalPaymentStrategy implements PaymentStrategy
{
public function pay(float $amount): bool
{
// Implement PayPal payment logic
return true;
}
public function getPaymentMethod(): string
{
return 'PayPal';
}
}
The Context Class
The `Context` class is responsible for interacting with the `PaymentStrategy` objects. It holds a reference to the current `PaymentStrategy` and delegates the payment processing to it:
php
namespace App\Services;
use App\Strategies\PaymentStrategy;
class PaymentContext
{
private $paymentStrategy;
public function setPaymentStrategy(PaymentStrategy $strategy)
{
$this->paymentStrategy = $strategy;
}
public function processPayment(float $amount): bool
{
return $this->paymentStrategy->pay($amount);
}
public function getPaymentMethod(): string
{
return $this->paymentStrategy->getPaymentMethod();
}
}
Usage in a Controller
In a controller, we can use the `PaymentContext` to process payments:
php
namespace App\Http\Controllers;
use App\Services\PaymentContext;
use App\Strategies\StripePaymentStrategy;
use App\Strategies\PaypalPaymentStrategy;
class PaymentController extends Controller
{
public function processPayment(float $amount)
{
$paymentContext = new PaymentContext();
// Set the payment strategy
$paymentContext->setPaymentStrategy(new StripePaymentStrategy());
// Process the payment
if ($paymentContext->processPayment($amount)) {
// Payment successful
return response()->json(['message' => 'Payment successful']);
} else {
// Payment failed
return response()->json(['message' => 'Payment failed'], 500);
}
}
public function changePaymentMethod(float $amount)
{
$paymentContext = new PaymentContext();
// Set a different payment strategy
$paymentContext->setPaymentStrategy(new PaypalPaymentStrategy());
// Process the payment with the new strategy
if ($paymentContext->processPayment($amount)) {
// Payment successful
return response()->json(['message' => 'Payment successful']);
} else {
// Payment failed
return response()->json(['message' => 'Payment failed'], 500);
}
}
}
By using the Strategy pattern, we can easily switch between different payment methods without modifying the `PaymentContext` class. If we need to add a new payment method in the future, we can create a new concrete `PaymentStrategy` implementation and use it in the `PaymentContext` without changing any existing code.
This promotes the Open/Closed Principle (OCP) of SOLID design principles, as the `PaymentContext` is open for extension (adding new payment methods) but closed for modification.
Citations:[1] https://ashallendesign.co.uk/blog/using-the-strategy-pattern-in-laravel
[2] https://github.com/robert-knepper/strategyDesignPattern
[3] https://muktar.tech/design-patterns-real-world-examples-in-laravel-django-and-express-js-6a91dce1dbba?gi=96bb15545f94
[4] https://www.reddit.com/r/PHP/comments/1cqetfy/laravel_under_the_hood_the_strategy_pattern/
[5] https://www.youtube.com/watch?v=0VlakXgCukM