Laravel: Usando try ... catch con DB :: transaction ()

81

Todos usamos DB::transaction()para múltiples consultas de inserción. Al hacerlo, ¿se try...catchdebe colocar una dentro o envolverla? ¿Es incluso necesario incluir un try...catchcuándo una transacción fallará automáticamente si algo sale mal?

Ejemplo de try...catchenvoltura de una transacción:

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

Todo lo contrario, DB::transaction()envolver un intento ... captura:

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

        // Do your SQL here

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

});

return is_null($exception) ? true : false;

O simplemente una transacción sin intentar ... atrapar

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

    // Do your SQL here

});

return is_null($exception) ? true : false;
mejorar
fuente

Respuestas:

179

En el caso de que necesite 'salir' manualmente de una transacción a través del código (ya sea a través de una excepción o simplemente verificando un estado de error), no debe usar, DB::transaction()sino que debe envolver su código en DB::beginTransactiony 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
}

Consulte los documentos de la transacción .

alexrussell
fuente
Después de revisarlo nuevamente, esta es la respuesta que estaba buscando. :)
enchance
@alexrussell - ¿La base de datos no genera una diferente \Exception? ¿Puedo capturarlo con este genérico \Exception? ¡Genial si lo es!
Artur Mamedov
¿Cuál es la diferencia entre DB::beginTransaction()y DB:transaction()?
Hamed Kamrava
2
Pregunta simple: ¿Qué sucede si no realiza una reversión después de la excepción o si no detecta la excepción? ¿Revertir automáticamente después del final del guión?
neoteknic
2
@HengSopheak esta pregunta era sobre las bases de datos de Laravel 4, por lo que es muy posible que mi respuesta ya no sea correcta para 5.3. Puede que valga la pena hacer una nueva pregunta con la etiqueta Laravel 5.3 para obtener el apoyo de la comunidad adecuado.
alexrussell
23

Si usa PHP7, use Throwable in catchpara detectar excepciones de usuario y errores fatales.

Por ejemplo:

DB::beginTransaction();

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

Si su código debe ser compartible con PHP5, use Exceptiony Throwable:

DB::beginTransaction();

try {
    DB::insert(...);    
    DB::commit();
} catch (\Exception $e) {
    DB::rollback();
    throw $e;
} catch (\Throwable $e) {
    DB::rollback();
    throw $e;
}
mnv
fuente
¿Qué pasa con el hecho de que DB :: beginTransaction () también puede arrojar \ Exception? ¿Debería incluirse en el try / catch?
Michael Pawlowsky
4
Si la transacción no se ha iniciado, no es necesario revertir nada. Además, no es bueno intentar deshacer la transacción no iniciada en catchbloque. Por lo tanto, un buen lugar DB::beginTransaction()es antes del trybloque.
lunes
11

Puede envolver la transacción sobre try..catch o incluso revertirlos, aquí mi código de ejemplo que solía usar en laravel 5, si mira profundamente DB:transaction()en Illuminate\Database\Connectioneso, es lo mismo que escribe una transacción manual.

Transacción 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;
    }

para que pueda escribir su código de esta manera y manejar su excepción, como devolver un mensaje a su formulario a través de flash o redirigir a otra página. RECUERDE el retorno dentro del cierre se devuelve en la transacción () por lo que si lo devuelve redirect()->back()no se redirigirá inmediatamente, porque devolvió la variable que maneja la transacción.

Ajustar transacción

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

entonces la alternativa es lanzar una variable booleana y manejar la función de redirección externa de la transacción o si necesita recuperar por qué falló la transacción, puede obtenerla desde $e->getMessage()adentrocatch(Exception $e){...}

Angga Ari Wijaya
fuente
Usé la
@hamidrezasamsami sí, la base de datos se revertió automáticamente, pero en algún momento debes saber si todas las consultas tienen éxito o no ..
Angga Ari Wijaya
6
El ejemplo de "Wrap Transaction" es incorrecto. Esto siempre se confirmará, incluso si una de las consultas falló porque todas las excepciones se detectan dentro de la devolución de llamada de la transacción. Quiere poner el try / catch fuera de DB :: transaction.
redmallard
2

Decidí dar una respuesta a esta pregunta porque creo que se puede resolver usando una sintaxis más simple que el intrincado bloque try-catch. La documentación de Laravel es bastante breve sobre este tema.

En lugar de usar try-catch, puede usar el DB::transaction(){...}contenedor de esta manera:

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

Entonces debería ver que el usuario y el registro de registro no pueden existir el uno sin el otro.

Algunas notas sobre la implementación anterior:

  • Asegúrese de realizar returnla transacción, para que pueda utilizar la response()devolución dentro de su devolución de llamada.
  • Asegúrese de hacer throwuna excepción si desea que la transacción se revierta (o tenga una función anidada que lance la excepción automáticamente, como una excepción SQL desde dentro de Eloquent).
  • El id, updated_at, created_aty cualesquiera otros campos están disponibles después de la creación para el $userobjeto (por la duración de esta transacción). La transacción se ejecutará a través de cualquier lógica de creación que tenga. SIN EMBARGO, todo el registro se descarta cuando AnyExceptionse lanza. Esto significa que, por ejemplo, una columna de incremento automático para idsí se incrementa en transacciones fallidas.

Probado en Laravel 5.8

Fuego
fuente
Es una locura que nadie haya mencionado este enfoque claro
Adam