Laravel depends on injection interface design

Assuming I need to do a payment service now, then I will first design an interface

interface PayInterface{

public function pay(Order $order): string;
}

Then implement this interface

class WeiXinPay implements PayInterface{

public function pay(Order $order) :string{
//Specific implementation
}
}

I started to find a problem.
WeChat payment requires three key parameters (appID, appSecret, key)
I will then modify the code, I hope these three parameters It was injected externally, rather than hard-coded in WeiXinPay, so I modified the WeiXinPay constructor and added an interface and implementation

interface PaySettingsInterface {

public function getSettings(): array;
}

class WeixinPaySettings implements PaySettingsInterface{
public function getSettings(): array{
return [
‘app_id’ => ‘xxxx’,
‘app_secret’ => ‘yyyyy’,
‘key’ => ‘zzzz’
];
}
}

class WeiXinPay implements PayInterface{

protected $settings;
public __construct(PaySettingsInterface $settings){
$this->settings = $settings->getSettings();
}

public function pay(Order $order) :string{
//Specific implementation
}
}

Okay. I feel that the implementation of PayInterface should be fine here. I started to write service providers

$this->app->bind(PaySettingsInterface::class,WeiXinPaySettings::class);

$this->app->bind(PayInterface::class, WeiXinPay::class);

Write a test code to run and see

public function testPay( ){

$orderSn = Strings::randomString(‘NUMBER+‘,12);
$order = factory(Order::class)->make([
‘sn’ => $orderSn,
‘uid’ => 111,
‘to_uid’ => 109,
‘relation_user’ => json_encode([109,108,107,104,102,12]),
‘amount’ => 1,
‘attach’ => 1,
‘status’ => Constans::ORDER_NO_PAY,
‘is_settle‘ => Constans::NO_SETTLE,
]);

/**
* @var $service PayInterface
*/
$service = $this->app->make(PayInterface::class);

$res = $service->pay($order);
$this->assertIsString($res);
}

No problem, everything is as expected. (In the future, I can easily switch from WeChat Pay to Alipay. I only need to switch the service provider.)

Two days later, there is a new demand. The ultimate problem is here. The boss hopes that the payee will be different every time you pay, that is to say, the appId, appSecret, and appKey of WeChat payment are different every time

I started to modify me The code, I thought: I can always pass in these variable parameters through the constructor.

interface PaySettingsInterface {

public function getSettings(): array;
}

class WeixinPaySettings implements PaySettingsInterface{
protected $appID;
protected $appKey;
protected $appSecret;

public function __construct($appID ,$appKey ,$appSecret){
$this->appID = $appID;
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}

public function getSettings(): array{
return [
‘app_id’ => $this->appID,
‘app_secret’ => $this->appSecret,
‘key’ => $this->appKey
];
}
}

class WeiXinPay implements PayInterface{

protected $settings;
public __construct(PaySettingsInterface $settings){
$this->settings = $settings->getSettings();
}

public function pay(Order $order) :string{
//Specific implementation
}
}

//Then I modify my service provider
$this->app->bind(PaySettingsInterface::class,function($app){
//How about new? The boss's needs may be different every time, and the data may come from the database or the cache.
$instance = new WeixinPaySettings(???);
return $instance;
});
$this->app->bind(PayInterface::class, WeiXinPay::class);
//At this point, it seems that it is impossible to automatically inject PaySettingsInterface through the container. Then I can only do this. In the test code:

public function testPay(){
$orderSn = Strings::randomString(‘NUMBER+‘,12);
$order = factory(Order::class)->make([
‘sn’ => $orderSn,
‘uid’ => 111,
‘to_uid’ => 109,
‘relation_user’ => json_encode([109,108,107,104,102,12]),
‘amount’ => 1,
‘attach’ => 1,
‘status’ => Constans::ORDER_NO_PAY,
‘is_settle‘ => Constans::NO_SETTLE,
]);

//Query the database to get the settings
$settings = Db::get();
$paySettings = new WeixinPaySettings($settings[‘app_id’],$settings[‘app_secret’],$settings[‘app_key’]);

$payService = new WeixinPay($paySettings);

$res = $payService->pay($order);
$this->assertIsString($res);
}

This looks ok, but I am troubled

  1. I can’t simply replace the payment method (WeixinPay is replaced by AliPay)
  2. The caller manually removes the relevant implementation of new, resulting in serious dependence.

I want to be able to:

  1. Able to simply replace the payment method (service provider)
  2. The caller only needs to call pay( Order $order) method can complete the payment, even if I switch the payment, the caller does not need to change it, conforms to the Richter substitution rule

Assuming I need to do a payment service now, then I will first design an interface

interface PayInterface{

public function pay(Order $order): string;
}

Then implement this interface

class WeiXinPay implements PayInterface{

public function pay(Order $order) :string{
//Specific implementation
}
}

I started to find a problem
WeChat Pay requires three key parameters (appID, appSecret, key)
I will modify the code, I hope these three parameters It was injected externally, rather than hard-coded in WeiXinPay, so I modified the WeiXinPay constructor and added an interface and implementation

interface PaySettingsInterface {

public function getSettings(): array;
}

class WeixinPaySettings implements PaySettingsInterface{
public function getSettings(): array{
return [
‘app_id’ => ‘xxxx’,
‘app_secret’ => ‘yyyyy’,
‘key’ => ‘zzzz’
];
}
}

class WeiXinPay implements PayInterface{

protected $settings;
public __construct(PaySettingsInterface $settings){
$this->settings = $settings->getSettings();
}

public function pay(Order $order) :string{
//Specific implementation
}
}

Okay. I feel that the implementation of PayInterface should be fine here. I started to write service providers

$this->app->bind(PaySettingsInterface::class,WeiXinPaySettings::class);

$this->app->bind(PayInterface::class, WeiXinPay::class);

Write a test code to run and see

public function testPay( ){

$orderSn = Strings::randomString(‘NUMBER+‘,12);
$order = factory(Order::class)->make([
‘sn’ => $orderSn,
‘uid’ => 111,
‘to_uid’ => 109,
‘relation_user’ => json_encode([109,108,107,104,102,12]),
‘amount’ => 1,
‘attach’ => 1,
‘status’ => Constans::ORDER_NO_PAY,
‘is_settle‘ => Constans::NO_SETTLE,
]);

/**
* @var $service PayInterface
*/
$service = $this->app->make(PayInterface::class);

$res = $service->pay($order);
$this->assertIsString($res);
}

No problem, everything is as expected. (In the future, I can easily switch from WeChat Pay to Alipay. I only need to switch the service provider.)

Two days later, there is a new demand. The ultimate problem is here. The boss hopes that the payee will be different every time you pay, that is to say, the appId, appSecret, and appKey of WeChat payment are different every time

I started to modify me The code, I thought: I can always pass in these variable parameters through the constructor.

interface PaySettingsInterface {

public function getSettings(): array;
}

class WeixinPaySettings implements PaySettingsInterface{
protected $appID;
protected $appKey;
protected $appSecret;

public function __construct($appID ,$appKey ,$appSecret){
$this->appID = $appID;
$this->appKey = $appKey;
$this->appSecret = $appSecret;
}

public function getSettings(): array{
return [
‘app_id’ => $this->appID,
‘app_secret’ => $this->appSecret,
‘key’ => $this->appKey
];
}
}

class WeiXinPay implements PayInterface{

protected $settings;
public __construct(PaySettingsInterface $settings){
$this->settings = $settings->getSettings();
}

public function pay(Order $order) :string{
//Specific implementation
}
}

//Then I modify my service provider
$this->app->bind(PaySettingsInterface::class,function($app){
//How about new? The boss's needs may be different every time, and the data may come from the database or the cache.
$instance = new WeixinPaySettings(???);
return $instance;
});
$this->app->bind(PayInterface::class, WeiXinPay::class);
//At this point, it seems that it is impossible to automatically inject PaySettingsInterface through the container. Then I can only do this. In the test code:

public function testPay(){
$orderSn = Strings::randomString(‘NUMBER+‘,12);
$order = factory(Order::class)->make([
‘sn’ => $orderSn,
‘uid’ => 111,
‘to_uid’ => 109,
‘relation_user’ => json_encode([109,108,107,104,102,12]),
‘amount’ => 1,
‘attach’ => 1,
‘status’ => Constans::ORDER_NO_PAY,
‘is_settle‘ => Constans::NO_SETTLE,
]);

//Query the database to get the settings
$settings = Db::get();
$paySettings = new WeixinPaySettings($settings[‘app_id’],$settings[‘app_secret’],$settings[‘app_key’]);

$payService = new WeixinPay($paySettings);

$res = $payService->pay($order);
$this->assertIsString($res);
}

This looks fine, but I am troubled

  1. I can’t easily replace the payment method (WeixinPay is replaced by AliPay)
  2. The caller manually removes the relevant implementation of new, resulting in serious dependence.

I hope to be able to:

  1. Able to easily replace the payment method (service provider)
  2. The caller only needs to call pay( Order $order) method can complete the payment, even if I switch the payment, the caller does not need to change it, conforms to the Richter substitution rule

Leave a Comment

Your email address will not be published.