Здравствуйте! На мой взгляд вы слишком увлеклись паттернами. Сегодня выходной, потрачу, пожалуй, немного времени и напишу свое видение решения проблемы кучи if-ов (хотя их не так уж и много)). Specification тут вам особо не поможет, так как проблема состоит в том, что у вас клиентский код как бы опрашивает объекты снаружи и принимает решения, а надо чтобы объекты сами принимали решения. Даже если вы примените Specification, классы specification все равно будут содержать много if-ов, так как вам понадобится куча интроспекций по типу. На мой взгляд If-вы рефакторятся полиморфизмом.
Решение примерное, в вашем решении может быть больше или меньше декораторов, в зависимости от того, как разделите проверки семантически.
Код: Выделить всё
public function handle(RequestWithdrawTransactionCommand $command): WithdrawTransaction
{
$user = $this->userRepository->get($command->getUserId());
if ($user === null) {
throw new DomainException('User is not found.')ж
}
$currency = Currency::get($command->getCurrency());
$amount = Amount::fromString($command->getAmount());
$invoice = new LimitAwareInvoice(
new CurrencyAvailabilityAwareInvoce(
new DefaultInvoice(
$amount,
$currency,
$command->getAddress(),
$user,
$this->transactionRepostory,
$this->currencyConfigRepository,
$this->confirmationCodeGenerator
),
$this->currencyConfigRepository
),
$this->withdrawChecker
);
return $invoice->makeTransaction();
}
interface Invoice
{
public function amount(): Amount;
public function currency(): Currency;
public function user(): User;
public function makeTransaction(): WithdrawTransaction;
}
class DefaultInvoice implements Invoice
{
/**
* @var Amount
*/
private $amount;
/**
* @var Currency
*/
private $currency;
/**
* @var string
*/
private $address;
/**
* @var User
*/
private $user;
/**
* @var TransactionRepostory
*/
private $transactionRepository;
/**
* @var CurrencyConfigRepository
*/
private $currencyConfigRepository;
/**
* @var ConfirmationCodeGenerator
*/
private $confirmationCodeGenerator;
/**
* DefaultInvoice constructor.
*
* @param Amount $amount
* @param Currency $currency
* @param string $address
* @param User $user
* @param TransactionRepostory $transactionRepository
* @param CurrencyConfigRepository $currencyConfigRepository
* @param ComnfirmationCodeGenerator $confirmationCodeGenerator
*/
public function __construct(
Amount $amount,
Currency $currency,
string $address,
User $user,
TransactionRepostory $transactionRepository,
CurrencyConfigRepository $currencyConfigRepository,
ComnfirmationCodeGenerator $confirmationCodeGenerator
) {
$this->amount = $amount;
$this->currency = $currency;
$this->address = $address;
$this->user = $user;
$this->transactionRepository = $transactionRepository;
$this->currencyConfigRepository = $currencyConfigRepository;
$this->confirmationCodeGenerator = $confirmationCodeGenerator;
}
public function makeTransaction(): WithdrawTransaction
{
$currencyConfig = $this->currencyConfigRepostitory->get($this->currency);
if ($currencyConfig === null) {
throw new DomainException('Currency config is not found.');
}
if ($this->amount->isNegative() || $this->amount->isZero()) {
throw new \DomainException('Wrong amount value');
}
return $this->transactionRepository->add(
$this->user,
$this->address,
$this->amount,
$this->currency,
$currencyConfig,
$this->confirmationCodeGenerator
);
}
public function amount(): Amount
{
return $this->amount;
}
public function currency(): Currency
{
return $this->currency;
}
public function user(): User
{
return $this->user;
}
}
abstract class DecoratedInvoice implements Invoice
{
/**
* @var Invoice
*/
protected $invoice;
/**
* DecoratedInvoice constructor.
*
* @param Invoice $invoice
*/
public function __construct(Invoice $invoice)
{
$this->invoice = $invoice;
}
public function amount(): Amount
{
return $this->invoice->amount();
}
public function currency(): Currency
{
return $this->invoice->currency();
}
public function user(): User
{
return $this->invoice->user();
}
}
class CurrencyAvailabilityAwareInvoice extends DecoratedInvoice
{
/**
* @var CurrencyConfigRepository
*/
private $currencyConfigRepository;
/**
* CurrencyAvailabilityAwareInvoice constructor.
*
* @param Invoice $invoice
* @param CurrencyConfigRepository $currencyConfigRepository
*/
public function __construct(
Invoice $invoice,
CurrencyConfigRepository $currencyConfigRepository
) {
$this->currencyConfigRepository = $currencyConfigRepository;
parent::__construct($invoice);
}
public function makeTransaction(): WithdrawTransaction
{
$currencyConfig = $this->currencyConfigRepository->getByCurrency($this->invoice->currency());
if ($currencyConfig === null) {
throw new DomainException('Currency config is not found.');
}
if (!$currencyConfig->isWithdrawEnabled()) {
throw new \DomainException('Withdraw disabled');
}
$currencyConfig->getWithdrawLimit()->assertAmount($this->invoice->amount());
return $this->invoice->makeTransaction();
}
}
class LimitAwareInvoice extends DecoratedInvoice
{
/**
* @var WithdrawChecker
*/
private $withdrawChecker;
/**
* LimitAwareInvoice constructor.
*
* @param Invoice $invoice
* @param WithdrawChecker $withdrawChecker
*/
public function __construct(
Invoice $invoice,
WithdrawChecker $withdrawChecker
) {
$this->withdrawChecker = $withdrawChecker;
parent::__construct($invoice);
}
public function makeTransaction(): WithdrawTransaction
{
if ($this->withdrawChecker->isReachLimit($this->invoice->user()->id(), $this->invoice->amount(), $this->invoice->currency())) {
throw new DomainException('Withdraw limit');
}
return $this->invoice->makeTransaction();
}
}
Подумайте, подойдет ли вам это, или лучше оставить один не такой уж и сложненький хендлер?
C if в Middleware ничего сделать особо не получится, Вы сами выбрали командную шину, а она фактически подразумевает интроспекции по типу.