Laravel: Використання try… catch з DB :: transaction ()


85

Ми всі використовуємо DB::transaction()для декількох запитів вставки. Роблячи це, чи try...catchслід поміщати всередину нього або обгортати його? Чи потрібно взагалі включати, try...catchколи транзакція автоматично провалиться, якщо щось піде не так?

Зразок try...catchобгортання транзакції:

// try...catch
try {
    // Transaction
    $exception = DB::transaction(function() {

        // Do your SQL here

    });

    if(is_null($exception)) {
        return true;
    } else {
        throw new Exception;
    }

}
catch(Exception $e) {
    return false;
}

Навпаки, DB::transaction()обгортання спробу ... улов:

// Transaction
$exception = DB::transaction(function() {
    // try...catch
    try {

        // Do your SQL here

    }
    catch(Exception $e) {
        return $e;
    }

});

return is_null($exception) ? true : false;

Або просто транзакція без спроби ... зловити

// Transaction only
$exception = DB::transaction(function() {

    // Do your SQL here

});

return is_null($exception) ? true : false;

Відповіді:


186

У випадку, якщо вам потрібно вручну `` вийти '' з транзакції через код (будь то виняток або просто перевірка стану помилки), ви не повинні використовувати, DB::transaction()а замість цього оберніть свій код в DB::beginTransactionта DB::commit/ DB::rollback():

DB::beginTransaction();

try {
    DB::insert(...);
    DB::insert(...);
    DB::insert(...);

    DB::commit();
    // all good
} catch (\Exception $e) {
    DB::rollback();
    // something went wrong
}

Перегляньте документи щодо транзакцій .


Подивившись ще раз, це відповідь, яку я шукав. :)
enchance

@alexrussell - База даних не генерує іншу \Exception? Я можу захопити його цим загальним \Exception? Чудово, якщо це так!
Артур Мамедов

Яка різниця між DB::beginTransaction()та DB:transaction()?
Хамед Камрава

2
Просте запитання: Що станеться, якщо ви не зробите відкат після винятку або якщо ви не вловите виняток? Автоматичний відкат після закінчення сценарію?
neoteknic

2
@HengSopheak це питання стосувалося баз даних Laravel 4, тому цілком можливо, що моя відповідь більше не правильна для 5.3. Можливо, вам варто поставити нове запитання з тегом Laravel 5.3, щоб отримати правильну підтримку спільноти.
alexrussell

25

Якщо ви використовуєте PHP7, використовуйте метальний в catchловити виключення користувачів і фатальні помилки.

Наприклад:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}

Якщо ваш код повинен бути порівнянним з PHP5, використовуйте Exceptionта Throwable:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Exception $e) {
    DB::rollback();
    throw $e;
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}

А що з того, що DB :: beginTransaction () може також видавати \ Exception? Чи повинен він бути включений у спробу / зловити?
Майкл Павловський,

4
Якщо транзакцію не розпочато, нам не потрібно нічого відмовляти. Більше того, не годиться пробувати відкат не запущеної транзакції в catchблоці. Тому гарне місце для DB::beginTransaction()знаходиться перед tryблоком.
mnv

12

Можна обертати угоду над try..catch або навіть звернути їх, ось мій приклад коду , який я використовував в Laravel 5 ,, якщо ви подивитеся глибоко всередині DB:transaction()в Illuminate\Database\Connectionтому , що те ж саме , як ви написати ручної операції.

Транзакція Laravel

public function transaction(Closure $callback)
    {
        $this->beginTransaction();

        try {
            $result = $callback($this);

            $this->commit();
        }

        catch (Exception $e) {
            $this->rollBack();

            throw $e;
        } catch (Throwable $e) {
            $this->rollBack();

            throw $e;
        }

        return $result;
    }

щоб ви могли писати свій код таким чином і обробляти свій виняток, наприклад, перекидати повідомлення назад у вашу форму за допомогою флеш-пам'яті або перенаправляти на іншу сторінку. ПАМ’ЯТАЙТЕ повернення всередині закриття повертається в транзакції (), тому, якщо ви повернете, redirect()->back()воно не буде перенаправляти негайно, оскільки воно повертається зі змінною, яка обробляє транзакцію.

Укладання транзакції

$result = DB::transaction(function () use ($request, $message) {
   try{

      // execute query 1
      // execute query 2
      // ..

      return redirect(route('account.article'));

   } catch (\Exception $e) {
       return redirect()->back()->withErrors(['error' => $e->getMessage()]);
    }
 });

// redirect the page
return $result;

тоді альтернатива - кинути булеву змінну і обробляти переспрямування за межами функції транзакції, або якщо вам потрібно отримати, чому транзакція не вдалася, ви можете отримати її $e->getMessage()зсерединиcatch(Exception $e){...}


Я використовував транзакцію без блоку try-catch, і це теж добре працювало
hamidreza samsami

@hamidrezasamsami так, база даних автоматично відкотилася, але іноді вам потрібно знати, чи всі запити успішні чи ні ..
Angga Ari Wijaya

7
Приклад "Транзакція обтікання" помилковий. Це завжди буде здійснено, навіть якщо один із запитів не вдався, оскільки всі винятки потрапляють у зворотний виклик транзакції. Ви хочете поставити try / catch поза межами транзакції DB ::.
redmallard

3

Я вирішив дати відповідь на це запитання, тому що, на мою думку, його можна вирішити за допомогою більш простого синтаксису, ніж заплутаний блок try-catch. Документація Laravel досить стисла на цю тему.

Замість того, щоб використовувати try-catch, ви можете просто використовувати DB::transaction(){...}обгортку наступним чином:

// MyController.php
public function store(Request $request) {
    return DB::transaction(function() use ($request) {
        $user = User::create([
            'username' => $request->post('username')
        ]);

        // Add some sort of "log" record for the sake of transaction:
        $log = Log::create([
            'message' => 'User Foobar created'
        ]);

        // Lets add some custom validation that will prohibit the transaction:
        if($user->id > 1) {
            throw AnyException('Please rollback this transaction');
        }

        return response()->json(['message' => 'User saved!']);
    });
};

Потім ви повинні побачити, що Користувач та запис журналу не можуть існувати один без одного.

Деякі примітки щодо реалізації вище:

  • Переконайтеся returnв транзакції, щоб ви могли використовувати той, response()який повертаєте, під час зворотного дзвінка.
  • Переконайтесь у throwвинятку, якщо ви хочете, щоб транзакція була відкочена (або маєте вкладену функцію, яка автоматично видає виняток для вас, як виняток SQL із Eloquent).
  • id, updated_at, created_atІ будь-які інші поля доступні після створення для $userоб'єкта (на час цієї операції). Транзакція буде виконуватися через будь-яку логіку створення, яку ви маєте. Однак, весь запис відкидається, коли його AnyExceptionкидають. Це означає, що, наприклад, стовпець автоматичного збільшення для idдійсно збільшується при невдалих транзакціях.

Випробувано на Laravel 5.8

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.