Laravel: Mit try… catch with DB :: transaction ()

81

Wir alle verwenden DB::transaction()für mehrere Einfügeabfragen. Sollte dabei ein Platz try...catchdarin platziert oder eingewickelt werden? Ist es überhaupt notwendig anzugeben, try...catchwann eine Transaktion automatisch fehlschlägt, wenn etwas schief geht?

Beispiel für das try...catchUmschließen einer Transaktion:

// 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;
}

Im Gegenteil, DB::transaction()ein Versuch einpacken ... fangen:

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

        // Do your SQL here

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

});

return is_null($exception) ? true : false;

Oder einfach eine Transaktion ohne zu versuchen ... zu fangen

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

    // Do your SQL here

});

return is_null($exception) ? true : false;
Enchance
quelle

Antworten:

179

Wenn Sie eine Transaktion manuell über Code beenden müssen (sei es durch eine Ausnahme oder einfach durch Überprüfen eines Fehlerzustands), sollten Sie DB::transaction()Ihren Code nicht verwenden, sondern stattdessen in DB::beginTransactionund DB::commit/ DB::rollback(): einschließen.

DB::beginTransaction();

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

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

Siehe die Transaktionsdokumente .

alexrussell
quelle
Nachdem ich es mir noch einmal angesehen habe, ist dies die Antwort, nach der ich gesucht habe. :)
Enchance
@alexrussell - Datenbank nicht anders generieren \Exception? Kann ich es mit diesem Generikum erfassen \Exception? Großartig, wenn es so ist!
Artur Mamedov
Was ist der Unterschied zwischen DB::beginTransaction()und DB:transaction()?
Hamed Kamrava
2
Einfache Frage: Was passiert, wenn Sie nach einer Ausnahme kein Rollback durchführen oder wenn Sie keine Ausnahme abfangen? Auto Rollback nach dem Ende des Skripts?
Neoteknic
2
@HengSopheak Diese Frage betraf Laravel 4-Datenbanken, daher ist es durchaus möglich, dass meine Antwort für 5.3 nicht mehr korrekt ist. Es könnte sich lohnen, eine neue Frage mit dem Laravel 5.3-Tag zu stellen, um die richtige Community-Unterstützung zu erhalten.
Alexrussell
23

Wenn Sie PHP7 verwenden, verwenden Sie Throwable in, catchum Benutzerausnahmen und schwerwiegende Fehler abzufangen .

Zum Beispiel:

DB::beginTransaction();

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

Wenn Ihr Code mit PHP5 kompatibel sein muss, verwenden Sie Exceptionund Throwable:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Exception $e) {
    DB::rollback();
    throw $e;
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}
mnv
quelle
Was ist mit der Tatsache, dass DB :: beginTransaction () möglicherweise auch \ Exception auslöst? Sollte es im Try / Catch enthalten sein?
Michael Pawlowsky
4
Wenn die Transaktion nicht gestartet wurde, müssen wir nichts zurücksetzen. Darüber hinaus ist es nicht sinnvoll, die nicht gestartete Rollback-Transaktion im catchBlock zu versuchen . Daher ist ein guter Platz DB::beginTransaction()vor dem tryBlock.
Mnv
11

Sie könnten die Transaktion um try..catch wickeln oder sogar umkehren, hier mein Beispielcode, den ich in Laravel 5 verwendet habe, wenn Sie genau so tief DB:transaction()in Illuminate\Database\Connectiondas Innere schauen , wie Sie eine manuelle Transaktion schreiben.

Laravel-Transaktion

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;
    }

So können Sie Ihren Code so schreiben und Ihre Ausnahme so behandeln, als würden Sie eine Nachricht per Flash zurück in Ihr Formular werfen oder auf eine andere Seite umleiten. Denken Sie daran, dass die Rückgabe innerhalb des Abschlusses in transaction () zurückgegeben wird. Wenn Sie redirect()->back()sie zurückgeben, wird sie nicht sofort umgeleitet, da sie an die Variable zurückgegeben wird, die die Transaktion verarbeitet.

Wrap-Transaktion

$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;

Dann besteht die Alternative darin, eine boolesche Variable zu werfen und die Umleitung außerhalb der Transaktionsfunktion durchzuführen. Wenn Sie herausfinden möchten, warum die Transaktion fehlgeschlagen ist, können Sie sie von $e->getMessage()innen abrufencatch(Exception $e){...}

Angga Ari Wijaya
quelle
Ich habe die Transaktion ohne Try-Catch-Block verwendet und es hat auch gut funktioniert
hamidreza samsami
@hamidrezasamsami Ja, die Datenbank wird automatisch zurückgesetzt, aber manchmal müssen Sie wissen, ob die Abfragen alle erfolgreich sind oder nicht.
Angga Ari Wijaya
6
Das Beispiel "Wrap Transaction" ist falsch. Dies wird immer festgeschrieben, auch wenn eine der Abfragen fehlgeschlagen ist, da alle Ausnahmen im Transaktionsrückruf abgefangen werden. Sie möchten den Versuch / Fang außerhalb der DB :: -Transaktion platzieren.
Redmallard
2

Ich habe beschlossen, eine Antwort auf diese Frage zu geben, da ich denke, dass sie mit einer einfacheren Syntax als dem verschlungenen Try-Catch-Block gelöst werden kann. Die Laravel-Dokumentation ist zu diesem Thema ziemlich kurz.

Anstatt try-catch zu verwenden, können Sie den DB::transaction(){...}Wrapper einfach wie folgt verwenden:

// 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!']);
    });
};

Sie sollten dann sehen, dass der Benutzer und der Protokolldatensatz nicht ohne einander existieren können.

Einige Hinweise zur Implementierung oben:

  • Stellen Sie returndie Transaktion sicher , damit Sie die response()Rückgabe innerhalb des Rückrufs verwenden können.
  • Stellen Sie throweine Ausnahme sicher, wenn Sie möchten, dass die Transaktion zurückgesetzt wird (oder wenn Sie eine verschachtelte Funktion haben, die die Ausnahme automatisch für Sie auslöst, wie eine SQL-Ausnahme aus Eloquent).
  • Die id, updated_at, created_atund alle anderen Felder sind verfügbar , nachdem CREATION für das $userObjekt (für die Dauer der Transaktion). Die Transaktion durchläuft eine beliebige Erstellungslogik. JEDOCH wird der gesamte Datensatz verworfen, wenn der AnyExceptiongeworfen wird. Dies bedeutet, dass beispielsweise eine Spalte für iddie automatische Inkrementierung für fehlgeschlagene Transaktionen inkrementiert wird.

Getestet auf Laravel 5.8

Flamme
quelle
Verrückt, dass niemand diesen klaren Ansatz erwähnt hat
Adam