0) Никаких TransactionBundle. Вы эту логику не сможете реюзать, а значит нет смысла делать бандл. Почитайте symfony best practice. У вас должен быть один AppBundle и все, больше ничего. Вы можете пытаться выносить какие-то части инфраструктуры, которая не привязана к бизнес логике в отдельные бандлы для последующего реюза, но бизнес логику приложения реюзать не выйдет.
1) почитайте про event sourcing. Этот способ хранения данных идеален для платежных транзакций, собственно в банках и т.д. этот подход и используют десятилетиями, да даже та же база данных хранит лог транзакций.
2) уберите flush их сервиса и вынесите его в контроллер. flush коммитит транзакцию в базу, и нам надо это делать когда мы завершили работу с оными а не "где-то посередине".
3) оборачивать это добро в еще одну транзакцию глупо, потому что... доктрина и так сделает транзакцию. В любом случае по хорошему это надо делать в декораторе.
4) call_user_func_array в вашем случае - пример плохого решения.
5) по умолчанию persist использовать нужно только для тех сущностей, которые мы только что создали (в нашем случае - транзакция), либо тех которые мы явно вынули из unit of work (а у нас нет вызова $em->detach).
6) EntityManager должен использоваться исключительно в репозитории и наружу гулять не должен. Все что касается доктрины должно быть изолировано от вашей логики. В этом самый большой плюс доктрины (абстракция от хранилища) и почему-то мало кто этим плюсом пользуется, толку тогда от доктрины....
7) сервисы менеджеры - отстой. Называйте сервисы нормально.
8) вместо кучи сервисов можно ввести разные объекты транзакций. Например FundTransaction, IncomTransaction и т.д. У вас же в сервисах почти весь код дублируется. А так можно было бы всю логику с этими операциями сложить прямо в сущности.
9) НИКАКИХ DIE! даже для дебага.
public function transactionAction(Request $request)
{
$data = $request->request;
$transactionDTO = new TransactionDTO(
// вообще я бы тут просто ID пользователя возвращал... но я упорот по изоляции приложения от UI
$this->get('security.token_storage')->getToken()->getUser(),
$data->get('sender_account_type'),
$data->get('recipient_account_type'),
$data->get('amount')
);
// с исключениями разберется фронт контроллер
$this->get('app.transaction_processor')->process($transactionDTO);
// вот теперь сохраняем изменения
$this->get('doctrine.orm.entity_manager')->flush();
return new Response(null, 201); // создали новую запись в журнале транзакций
}
class TransactionProcessor
{
private $transactionsRepository;
public function __construct(TransactionRepository $repository)
{
$this->transactionsRepository = $repository;
}
public function process(TransactionDTO $dto)
{
// create это статический метод фабрика у абстрактного класса Transaction
// читать шаблон проектирования "абстрактная фабрика".
$transaction = Transaction::create($dto->getSender(), $dto->getRecipient(), $dto->getAmount());
$this->transactionsRepository->add($transaction);
}
}
дальше мне по логике не понятно, почему у вас одна транзакция на двух человек, полюбому у sender-а будет один тип транзакции а у ресивера другой. Можно запомнить кому мы чего передавали и только.