Benjy1997's avatar

Transaction Helper

Hello everyone.

To respect dry design, I would like to simplify the code used to manage transactions.

I developed a personal solution using Helpers that includes the code submitted to the transaction.

Service called by the controller using the helper :

public function store(Array $data){

        $transaction = function ($data){
            $serviceOwner = ServiceOwner::findOrFail($data['proprietaire_de_services_id']);

            $inputValue = new InputValue($data);
            $inputValue->audit_utilisateur_creation = Auth::user()->initiales;
            $inputValue->save();

            // Ajoute les valeurs dans la table d'historique.
            $inputValue->serviceOwners()->attach($serviceOwner->id, [
                'date_heure_validation' => date('Y-m-d H:i:s'),
                'audit_utilisateur_creation' => Auth::user()->initiales
            ]);

            return $inputValue;
        };

        $inputValue = \TransactionHelper::useTransaction($transaction, $data);

        return new InputValueResource($inputValue);
    }

Helper :

 public static function useTransaction($transaction, $data)
    {
        if (is_callable($transaction)) {
            DB::connection(DB::getDefaultConnection())->beginTransaction();
            try {
                $model = $transaction($data);
                DB::connection(DB::getDefaultConnection())->commit();
                return $model;
            } catch (\Exception $exception) {
                DB::connection(DB::getDefaultConnection())->rollBack();
                throw $exception;
            }

        } else {

            return null;
        }
    }

Service called by the controller without the helper :

public function store(Array $data){

        $this->getDBConnexion()->beginTransaction();
        try {
            $serviceOwner = ServiceOwner::findOrFail($data['proprietaire_de_services_id']);

            $inputValue = new InputValue($data);
            $inputValue->audit_utilisateur_creation = Auth::user()->initiales;
            $inputValue->save();

            // Ajoute les valeurs dans la table d'historique.
            $inputValue->serviceOwners()->attach($serviceOwner->id, [
                'date_heure_validation' => date('Y-m-d H:i:s'),
                'audit_utilisateur_creation' => Auth::user()->initiales
            ]);

            $this->getDBConnexion()->commit();
            return new InputValueResource($inputValue);

            // @codeCoverageIgnoreStart
        } catch (Exception $exception) {
            $this->getDBConnexion()->rollback();
            throw $exception;
            // @codeCoverageIgnoreEnd
        }
    }

I'd like to know if this is a good way to do it or not

thank you for your answers.

0 likes
1 reply
rodrigo.pedra's avatar
Level 56

I use a middleware for that:

<?php

namespace App\Http\Middleware;

use Closure;
use Exception;
use Illuminate\Database\ConnectionInterface;
use Symfony\Component\HttpFoundation\Response;

class DbTransaction
{
    /** @var \Illuminate\Database\ConnectionInterface */
    private $connection;

    public function __construct(ConnectionInterface $connection)
    {
        $this->connection = $connection;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     * @throws \Throwable
     */
    public function handle($request, Closure $next)
    {
        $this->connection->beginTransaction();

        try {
            $response = $next($request);
        } catch (Exception $exception) {
            $this->connection->rollBack();
            throw $exception;
        }

        if ($response instanceof Response && $response->getStatusCode() > 399) {
            $this->connection->rollBack();
        } else {
            $this->connection->commit();
        }

        return $response;
    }
}

Add this you controller constructor:

namespace App\Http\Controllers;

use App\Http\Middleware\DbTransaction;

class MyController extends Controller
{
    public function __construct()
    {
        $this->middleware(DbTransaction::class)->only(['store']);
    }

    // ... controller methods
}

Here I am using the middleware class directly in the controller's constructor, but you can add it to app/Http/Kernel.php with an alias and use the alias.

Please or to participate in this conversation.