Home > OS >  Select interface implementation according to parameter
Select interface implementation according to parameter

Time:06-30

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.

  • Related