Laravel: Używanie try… catch z DB :: transaction ()

85

Wszyscy używamy DB::transaction()do wielu zapytań wstawiania. Czy robiąc to, należy try...catchumieścić w środku, czy owinąć? Czy w ogóle konieczne jest uwzględnienie, try...catchkiedy transakcja automatycznie się nie powiedzie, jeśli coś pójdzie nie tak?

Przykładowe try...catchopakowanie transakcji:

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

Wręcz przeciwnie, DB::transaction()zawijanie próba ... haczyka:

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

        // Do your SQL here

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

});

return is_null($exception) ? true : false;

Lub po prostu transakcja bez próby ... złapania

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

    // Do your SQL here

});

return is_null($exception) ? true : false;
urok
źródło

Odpowiedzi:

186

W przypadku, gdy musisz ręcznie `` wyjść '' z transakcji za pomocą kodu (czy to przez wyjątek, czy po prostu sprawdzając stan błędu), nie powinieneś używać, DB::transaction()ale zamiast tego zawiń kod DB::beginTransactioni 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
}

Zobacz dokumentację transakcji .

alexrussell
źródło
Po ponownym przejrzeniu, to jest odpowiedź, której szukałem. :)
enchance
@alexrussell - Baza danych nie generuje innego \Exception? Czy mogę to uchwycić tym generycznym \Exception? Świetnie, jeśli to jest!
Artur Mamedov
Jaka jest różnica między DB::beginTransaction()i DB:transaction()?
Hamed Kamrava
2
Proste pytanie: co się stanie, jeśli nie wykonasz wycofania zmian po wyjątku lub jeśli nie złapiesz wyjątku? Automatyczne wycofywanie po zakończeniu skryptu?
neoteknic
2
@HengSopheak to pytanie dotyczyło baz danych Laravel 4, więc jest całkiem możliwe, że moja odpowiedź nie jest już poprawna dla 5.3. Warto zadać nowe pytanie za pomocą tagu Laravel 5.3, aby uzyskać odpowiednie wsparcie społeczności.
alexrussell
25

Jeśli używasz PHP7, użyj Throwable in catchdo wychwytywania wyjątków użytkowników i błędów krytycznych.

Na przykład:

DB::beginTransaction();

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

Jeśli Twój kod musi być porównywalny z PHP5, użyj Exceptioni Throwable:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Exception $e) {
    DB::rollback();
    throw $e;
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}
Nacięcie
źródło
A co z faktem, że DB :: beginTransaction () może również zgłosić \ Exception? Czy powinien być uwzględniony w try / catch?
Michael Pawlowsky
4
Jeśli transakcja nie została rozpoczęta, nie musimy niczego wycofywać. Co więcej, nie warto próbować wycofać transakcji nie rozpoczętej w catchbloku. Dlatego dobre miejsce DB::beginTransaction()jest przed tryblokiem.
Nick
12

Można owijania transakcję ponad try..catch lub nawet je odwrócić, oto mój przykładowy kod użyłem w laravel 5 ,, jeśli spojrzeć głęboko wewnątrz DB:transaction()w Illuminate\Database\Connectionktóre tak samo jak piszesz ręczną transakcję.

Laravel Transaction

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

więc możesz napisać swój kod w ten sposób i obsłużyć wyjątek, na przykład wrzucić wiadomość z powrotem do formularza przez flash lub przekierować na inną stronę. PAMIĘTAJ, że zwrot wewnątrz zamknięcia jest zwracany w transakcji (), więc jeśli wróciszredirect()->back() , nie przekieruje od razu, ponieważ wróciło do zmiennej obsługującej transakcję.

Zawiń transakcję

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

wtedy alternatywą jest rzucanie zmiennej boolowskiej i obsługa przekierowania na zewnątrz funkcji transakcji lub jeśli potrzebujesz dowiedzieć się, dlaczego transakcja się nie powiodła, możesz to uzyskać od $e->getMessage()wewnątrzcatch(Exception $e){...}

Angga Ari Wijaya
źródło
Skorzystałem z transakcji bez bloku try-catch i to też działało dobrze
hamidreza samsami
@hamidrezasamsami tak, baza automatyczny wycofana, ale kiedyś trzeba wiedzieć wszystko są pytania, czy nie uda ..
Angga Ari Wijaya
7
Przykład „Opakowania transakcji” jest nieprawidłowy. To zawsze zostanie zatwierdzone, nawet jeśli jedno z zapytań zakończyło się niepowodzeniem, ponieważ wszystkie wyjątki zostały przechwycone w wywołaniu zwrotnym transakcji. Chcesz umieścić try / catch poza DB :: transaction.
redmallard
3

Postanowiłem udzielić odpowiedzi na to pytanie, ponieważ myślę, że można to rozwiązać za pomocą prostszej składni niż zawiły blok try-catch. Dokumentacja Laravel jest dość krótka na ten temat.

Zamiast używać try-catch, możesz po prostu użyć DB::transaction(){...}opakowania w następujący sposób:

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

Powinieneś wtedy zobaczyć, że rekord użytkownika i dziennika nie mogą istnieć bez siebie.

Kilka uwag na temat powyższej implementacji:

  • Upewnij się, returnże dokonałeś transakcji, abyś mógł użyć response()zwrotu w ramach jej callback.
  • Upewnij się, że throwistnieje wyjątek, jeśli chcesz, aby transakcja została wycofana (lub mieć zagnieżdżoną funkcję, która automatycznie zgłasza wyjątek, jak wyjątek SQL z poziomu Eloquent).
  • id, updated_at, created_atOraz wszelkie inne pola są dostępne po utworzeniu dla $userobiektu (na czas trwania tej transakcji). Transakcja zostanie przeprowadzona według dowolnej logiki tworzenia, którą posiadasz. JEDNAK, cały rekord jest odrzucany, gdy AnyExceptionjest rzucany. Oznacza to, że na przykład kolumna automatycznego zwiększania wartości idjest zwiększana w przypadku nieudanych transakcji.

Testowane na Laravel 5.8

Płomień
źródło