I'm creating an interface to allow switching between various direct debit providers.
I've hit a stumbling block with the way that they / SOAP APIs require the updating of a record, they require you to pass in the whole object rather than just update a single value.
The issue here is that the different API's use different names for their fields, take the following example:
$directDebitInterface->updateAccount($reference, [
'AccountName' => 'NEW ACCOUNT NAME',
]);
Now, the current direct debit provider uses 'AccountName' but another references 'AccountHolderName'.
If I'm passing in the field to be updated but I don't know which provider I'll be interfacing to, how do I know which field to map this to?
Is there a technique that maps the field names for each provider?
My first thought is to use constants in each provider's class that inherits the interface (what is the name for that btw?)
E.g.
Provider 1
const ACCOUNT_NAME = 'AccountHolderName';
public function update($newValue)
{
// logic to post, pseudo array
[
self::ACCOUNT_NAME => $newValue
]
}
My issue now comes down to how to pass in the correct field as it ends up the same issue, I'll need to create some sort of mapping between what I call a field and how it is on the provider's end.
Thanks,
CodePudding user response:
You should stick to one name in your code base. The communication to the external entity (direct debit provider) should be in one class per provider, and those classes need to implement an interface so it doesn't matter in the rest of the code which class is actually used.
The connector class (or whatever you want to call it) needs to do the mapping from your naming scheme to the scheme of the external party when sending data, and back when receiving data. The naming scheme of the external party should not leak into the rest of your code.
CodePudding user response:
One way to do it is to use an interface throughout your code and let Laravel bind a specific implementation to use. The basic steps are,
- Create an interface
- Create an abstract class / Implement interface
- Extend the abstract class
- Bind to a specific implementation
- Use the interface
Create an interface
Create an interface for each debit provider. You can probably think of a better name but I used BankInterface
.
interface BankInterface
{
public function updateAccount(): void;
public function createPayload(): array;
}
Create an abstract class / Implement interface
The abstract class implements the interface. Since it is abstract you know that there must be child classes that further extend it.
abstract class Bank implements BankInterface
{
protected $account = '';
protected $amount = 0;
public function updateAccount(): void
{
// @todo implement the update
}
}
Extend the abstract class
This is where each one "knows" about what property names to use.
class BankA extends Bank
{
public function createPayload(): array
{
return [
'AccountName' => $this->account,
'Amount' => $this->amount,
];
}
}
class BankB extends Bank
{
public function createPayload(): array
{
return [
'AccountHolderName' => $this->account,
'AmountToDebit' => $this->amount,
];
}
}
class BankC extends Bank
{
public function createPayload(): array
{
return [
'Identifier' => $this->account,
'AmountInCents' => $this->amount * 100,
];
}
}
Bind to a specific implementation
Create a new entry in your .env file. This will control which specific implementation you are using.
DIRECT_DEBIT_PROVIDER=BankB
Now, bind to a specific implementation based on a value in your .env
file. You are free to use any method to determine which implementation to use. You are not restricted to an env file.
class DirectDebitProvider extends ServiceProvider
{
public function register()
{
$provider = match(env('DIRECT_DEBIT_PROVIDER')) {
'BankA' => BankA::class,
'BankB' => BankB::class,
'BankC' => BankC::class,
default => BankA::class,
};
$this->app->bind(BankInterface::class, $provider);
}
}
Register DirectDebitProvider
in config/app.php
...
'providers' => [
...,
App\Providers\DirectDebitProvider ::class,
...,
],
...
Use the interface
Finally, use, by type-hinting, BankInterface
throughout your codebase. Not entirely sure what $reference
refers to here, but you can probably consolidate this whole thing into $bank->update()
and let the abstract class construct the payload/reference, but I'll leave that to you.
public function index(BankInterface $bank) {
$bank->updateAccount($reference, $bank->createPayload());
}