What is correct way to select interface implementation according to passed parameter?
Let's have following code:
interface PaymentProcessor {
public function process(Order $order): void;
}
class CardPayment implements PaymentProcessor {}
class BankTransfer implements PaymentProcessor {}
class OrderService {
public function __construct( // Load payment processors from DI
private CardPayment $cardPayment,
private BankTransfer $bankTransfer,
) {}
// This is what is not clear to me
private function getPaymentProcessor(string $paymentMethod): PaymentProcessor
{
return match ($paymentMethod) {
'card' => $this->cardPayment,
'bank' => $this->bankTransfer,
}
}
public function payOrder(Order $order): void
{
$processor = $this->getPaymentProcessor($order->getPaymentMethod());
$processor->process($order);
}
}
What is not clear to me is how can I get PaymentProcessor by payment method name.
I would use code above, probably extracted to some "Factory". But according to answer at PHP - Using Interfaces, Strategy Pattern and Optional Method Parameters I am probably breaking open/close principle. Then what should be correct way?
CodePudding user response:
The correct way to do this could be combination of Strategy pattern and Dependency Injection.
Solution posted in reffered answer is correct, but is missing one important point - the trick is that all the mapping can be done via Dependency Injection.
So you can have
interface PaymentStrategy {
public function process(): void;
}
class CardPayment implements PaymentStrategy {
public function process(): void;
}
class BankTransfer implements PaymentStrategy {
public function process(): void;
}
class PaymentService {
private PaymentStrategy $strategy;
/** @var array<string, PaymentStrategy> $processors */
public function __construct(private array $processors) {
}
public function setStrategy(string $paymentMethod): void {
if (!array_key_exists($paymentMethod, $this->processors)) {
throw new \InvalidArgumentException('Processor not found');
}
$this->strategy = $this->processors[$paymentMethod];
}
public function process(): void {
$this->strategy->process();
}
}
So all you need to do is convince your DI container to give you all of PaymentStrategy implementations. Ideally in paymentMethodName - strategy format. Alternatively you can add some function to interface and class which return it's payment method name - and then run foreach - if - return.