Setup
We have interface:
<?php
namespace App\Payment;
interface PaymentProcessorInterface
{
public function processPayment(float $amount, string $currency): bool;
}
Implementation 1:
<?php
// src/Payment/PaypalPaymentProcessor.php
namespace App\Payment;
class PaypalPaymentProcessor implements PaymentProcessorInterface
{
public function processPayment(float $amount, string $currency): bool
{
echo "Processing \${$amount} {$currency} payment through PayPal.\n";
return true;
}
}
Implementation 2:
<?php
// src/Payment/StripePaymentProcessor.php
namespace App\Payment;
class StripePaymentProcessor implements PaymentProcessorInterface
{
public function processPayment(float $amount, string $currency): bool
{
echo "Processing \${$amount} {$currency} payment through Stripe.\n";
return true;
}
}
Factory:
<?php
// src/Payment/PaymentProcessorFactory.php
namespace App\Payment;
use InvalidArgumentException;
class PaymentProcessorFactory
{
public function __construct(
private PaypalPaymentProcessor $paypalProcessor,
private StripePaymentProcessor $stripeProcessor
) {
}
public function create(string $paymentType): PaymentProcessorInterface
{
switch ($paymentType) {
case 'paypal':
return $this->paypalProcessor;
case 'stripe':
return $this->stripeProcessor;
default:
throw new InvalidArgumentException("Invalid payment processor type: {$paymentType}");
}
}
}
In .env we have the variable for the default payment type.
PAYMENT_TYPE=stripe
Solution
Wire this in services.yaml like this (presuming you are using default autowire: true, autoconfigure: true):
App\Payment\PaymentProcessorInterface:
factory: ['@App\PaymentProcessorFactory', "create"]
arguments:
$dataProviderType: "%env(PAYMENT_TYPE)%"
-
First we create a service id for configuring interface object creation. We are using fully qualified class name.
-
Because interface doesn’t have constructor we need to tell symfony how to create an object. For that there is
factorydefinition key which tells what specific class and method is used to create object, in this case interface. -
Fill out factory parameters:
[$service, $method].-
Because we are using non static factory we need to use registered service instead of class. And because we have autowiring true our factory is already registered as service and we can refer to it with
@like this:@App\PaymentProcessorFactory. If the factory is using static method we would only need to write:App\PaymentProcessorFactory. -
for
$methodwe use our methodcreate
-
-
To pass arguments to the factory method we use
argumentskey and then explicitly define variable name and value which is used in factory method which is$paymentType. -
Payment type we get from environment variable
PAYMENT_TYPEwhich we can use using symfony yaml syntax%env(PAYMENT_TYPE)%.
Complete service.yaml now looks like this:
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: "../src/"
exclude:
- "../src/DependencyInjection/"
- "../src/Entity/"
- "../src/Kernel.php"
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Payment\PaymentProcessorInterface:
factory: ['@App\PaymentProcessorFactory', "create"]
arguments:
$dataProviderType: "%env(PAYMENT_TYPE)%"