Как покрыть транзакцией afterAction вместе с самим action в Yii2?

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
maksimepikhin
Сообщения: 3
Зарегистрирован: 2023.01.20, 11:45
Откуда: Москва

Как покрыть транзакцией afterAction вместе с самим action в Yii2?

Сообщение maksimepikhin »

Есть базовый REST контроллер (свои контроллер наследник yii-шного), в котором есть beforeAction и afterAction. В afterAction есть логика записи лога в историю изменений (таблица в БД). При запросе DELETE что-то там в afterAction происходит ошибка (предположим). Требуется откатить изменение, которое произошло в DeleteAction при запросе. То есть, обеспечить полный откат всего запроса по БД. Как это сделать?

Сейчас есть в beforeAction:

Код: Выделить всё

$transaction = Yii::$app->db->beginTransaction();
$this->on(self::EVENT_AFTER_ACTION, [$transaction, 'commit']);

return parent::beforeAction($action);
В afterAction:

Код: Выделить всё

History::write(
    $this->historyTable,
    $entityId,
    $action->id,
    $historyData
);
DeleteAction - без изменений.

Получается, что если в afterAction базового класса (кастомный) случился exception, то запись удаляется, но ответ будет 500 с ошибкой. Нужно, чтобы запись не удалялась.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Как покрыть транзакцией afterAction вместе с самим action в Yii2?

Сообщение SiZE »

Вынести работу с транзакциями в сервисный слой

Код: Выделить всё

interface TransactionManager
{
    public function begin();
    
    public function commit();
    
    public function rollback();
}

interface Handler
{
    public function handle();
}

class DeleteHandler extends \yii\db\ActiveRecord implements Handler
{
    
    public function handle()
    {
        if ($this->isNewRecord) {
            throw new RuntimeException('Model should not be a new record');
        }
        
        $this->delete();
    }
}

interface ServiceLog
{
    public function log();
}

class Service
{
    private TransactionManager $transactionManager;
    private ServiceLog $serviceLog;
    
    public function __construct(TransactionManager $transactionManager, ServiceLog $serviceLog)
    {
        $this->transactionManager = $transactionManager;
        $this->serviceLog = $serviceLog;
    }
    
    public function handle(Handler $handler): void
    {
        try {
            $this->transactionManager->begin();
            $handler->handle();
            $this->serviceLog->log();
            $this->transactionManager->commit();
        } catch (\Throwable $e) {
            $this->transactionManager->rollback();
            
            throw $e;
        }
    }
}

Код: Выделить всё

public function deleteAction(int $id, Service $service)
{
    $model = DeleteHandler::find()->andWhere(['id' => $id])->one();
    if (!$model) {
        throw new \yii\web\NotFoundHttpException();
    }
    
    try {
        $service->handle($model);
    } catch (\Throwable $e) {
    
        // обработать ошибки если надо
    }
}
Ответить