The Problem π«
Ever seen code like this?
public function processPayment(Request $request)
{
$method = $request->payment_method;
if ($method === 'credit_card') {
// 50 lines of credit card logic...
} elseif ($method === 'paypal') {
// 40 lines of PayPal logic...
} elseif ($method === 'crypto') {
// 60 lines of crypto logic...
} // ... more conditions
}
This creates:
- 200+ line methods
- Untestable code
- Maintenance nightmares
- SOLID violations
The Solution: Strategy Pattern β¨
The Strategy Pattern encapsulates algorithms into separate classes, making them interchangeable.
Step 1: Define the Interface
interface PaymentStrategyInterface
{
public function pay(float $amount, array $details): array;
public function validate(array $details): bool;
public function refund(string $transactionId, float $amount): array;
}
Step 2: Create Concrete Strategies
class CreditCardStrategy implements PaymentStrategyInterface
{
public function pay(float $amount, array $details): array
{
// Credit card processing logic
return [
'success' => true,
'transaction_id' => 'txn_' . uniqid(),
];
}
public function validate(array $details): bool
{
return !empty($details['card_number']) &&
!empty($details['cvv']);
}
public function refund(string $transactionId, float $amount): array
{
// Refund logic
return ['success' => true];
}
}
class PayPalStrategy implements PaymentStrategyInterface
{
public function pay(float $amount, array $details): array
{
// PayPal processing logic
return [
'success' => true,
'transaction_id' => 'pp_' . uniqid(),
];
}
public function validate(array $details): bool
{
return !empty($details['email']) &&
filter_var($details['email'], FILTER_VALIDATE_EMAIL);
}
public function refund(string $transactionId, float $amount): array
{
return ['success' => true];
}
}
Step 3: Create Strategy Factory
class PaymentStrategyFactory
{
public function create(string $method): PaymentStrategyInterface
{
return match($method) {
'credit_card' => new CreditCardStrategy(),
'paypal' => new PayPalStrategy(),
'crypto' => new CryptoStrategy(),
default => throw new \InvalidArgumentException("Unknown method: {$method}"),
};
}
}
Step 4: Use in Controller
class OrderController extends Controller
{
public function __construct(
private PaymentStrategyFactory $factory
) {}
public function processPayment(Request $request)
{
$strategy = $this->factory->create($request->payment_method);
if (!$strategy->validate($request->payment_details)) {
return response()->json(['error' => 'Invalid details'], 400);
}
$result = $strategy->pay(
$request->amount,
$request->payment_details
);
return response()->json($result);
}
}
Benefits π―
β
Single Responsibility - Each strategy handles one method
β
Open/Closed - Add new methods without modifying existing code
β
Easy Testing - Test each strategy independently
β
Maintainable - Clear separation of concerns
β
Flexible - Swap strategies at runtime
Testing Example
class CreditCardStrategyTest extends TestCase
{
public function test_validates_card_details(): void
{
$strategy = new CreditCardStrategy();
$valid = $strategy->validate([
'card_number' => '4532015112830366',
'cvv' => '123',
]);
$this->assertTrue($valid);
}
}
Real-World Use Cases π‘
Use Strategy Pattern for:
- Payments - Credit cards, PayPal, Crypto
- Storage - S3, Local, DigitalOcean
- Notifications - Email, SMS, Slack
- Authentication - OAuth, LDAP, JWT
- Export - PDF, Excel, CSV
Read the Full Article π
Want the complete implementation with advanced techniques, more examples, and best practices?
Read the full article on Medium
The full version includes:
- Complete payment gateway integrations
- Advanced dynamic strategy selection
- Comprehensive unit tests
- Service provider configuration
- Error handling patterns
- Real production examples
Quick Takeaway
Transform this mess:
if ($method === 'x') { /* 50 lines */ }
elseif ($method === 'y') { /* 40 lines */ }
Into this elegance:
$strategy = $factory->create($method);
$result = $strategy->pay($amount, $details);
Your code will thank you! π
What design patterns do you use in Laravel? Share in the comments!
Top comments (0)