Database Transactions are essential if you want to perform a few DB insert/update/delete operations and ensure the integrity of the data. Let's take a look at 5 different examples from Laravel open-source projects.
1. themsaid/ergodnc: Create Office with Tags
The code of the Controller:
class OfficeController extends Controller{ public function create(): JsonResource { $attributes = (new OfficeValidator())->validate( $office = new Office(), request()->all() ); $attributes['approval_status'] = Office::APPROVAL_PENDING; $attributes['user_id'] = auth()->id(); $office = DB::transaction(function () use ($office, $attributes) { $office->fill( Arr::except($attributes, ['tags']) )->save(); if (isset($attributes['tags'])) { $office->tags()->attach($attributes['tags']); } return $office; }); // ... }}
As you can see, the Office
model new record is created, and then, with the many-to-many relation ->attach()
method, DB Transaction ensures that if tag attaching fails, then the Office new record is also rolled back.
See the original code on GitHub.
2. JuanDMeGon/Laravel-from-Scratch: Many-To-Many with Deadlocks
The code of the Controller:
class OrderController extends Controller{ public function store(Request $request) { return DB::transaction(function() use($request) { $user = $request->user(); $order = $user->orders()->create([ 'status' => 'pending', ]); $cart = $this->cartService->getFromCookie(); $cartProductsWithQuantity = $cart ->products ->mapWithKeys(function ($product) { $quantity = $product->pivot->quantity; if ($product->stock < $quantity) { throw ValidationException::withMessages([ 'cart' => "There is not enough stock for the quantity you required of {$product->title}", ]); } $product->decrement('stock', $quantity); $element[$product->id] = ['quantity' => $quantity]; return $element; }); $order->products()->attach($cartProductsWithQuantity->toArray()); return redirect()->route('orders.payments.create', ['order' => $order->id]); }, 5); }}
Let's discuss what transaction is happening here, and why.
- We create the
Order
- Then we decrement the stock of
$product
- Finally, we attach Products to the
$order
Interesting thing here: did you know you can add a number parameter to the DB::transaction()
method?
See above:
DB::transaction(function() use($request) { // ... whatever is inside}, 5);
What does that 5
do?
Quote from the original Laravel docs:
The transaction
method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown.
See the original code on GitHub.
3. amitavroy/doctor-app
The code of the Service class:
class PatientService{ public function createPatient($patientData): Patient { $settingService = app()->make(SettingService::class); try { DB::beginTransaction(); $patientNumber = $settingService->getNextPatientNumber(); $patient = Patient::create([ 'patient_id' => now()->format('Ym') . $patientNumber, 'name' => $patientData['name'], 'phone_number' => $patientData['phone_number'], 'year_of_birth' => now()->subYears($patientData['age'])->format('Y'), 'weight' => $patientData['weight'], ]); $settingService->incrementLastPatientNumber(); DB::commit(); return $patient; } catch (Exception $exception) { logger()->error($exception->getMessage()); } }}
This one is interesting: it uses beginTransaction()
and commit()
manually, and also wraps the transaction in a try .. catch
statement, additionally.
See the original code on GitHub.
4. pixelfed/pixelfed: Delete Everything Safely
The code of the Job class:
class DeleteRemoteProfilePipeline implements ShouldQueue{ // ... public function handle() { $profile = $this->profile; DB::transaction(function() use ($profile) { $profile->avatar->forceDelete(); $id = $profile->id; MediaTag::whereProfileId($id)->delete(); StatusHashtag::whereProfileId($id)->delete(); DirectMessage::whereFromId($id)->delete(); FollowRequest::whereFollowingId($id) ->orWhere('follower_id', $id) ->forceDelete(); Follower::whereProfileId($id) ->orWhere('following_id', $id) ->forceDelete(); Like::whereProfileId($id)->forceDelete(); }); }}
This is pretty straightforward. There are so many things related to the profile, so there's quite a big chance of failure if some of those records malfunction during the deletion process. So DB Transaction is a no-brainer here.
More things are happening in this Job, so see the original code on GitHub.
5. akaunting/akaunting
The code of a custom PHP Action class method:
class Reconciliations extends BulkAction{ public function reconcile($request) { $reconciliations = $this->getSelectedRecords($request); foreach ($reconciliations as $reconciliation) { \DB::transaction(function () use ($reconciliation) { $reconciliation->reconciled = 1; $reconciliation->save(); Transaction::where('account_id', $reconciliation->account_id) ->isNotReconciled() ->whereBetween('paid_at', [$reconciliation->started_at, $reconciliation->ended_at])->each(function ($item) { $item->reconciled = 1; $item->save(); }); }); } } }
Reconciliation is a financial term for "merging transactional records" or something like that.
So, we need to mark the reconciliation as saved, but also need to mark individual transactions as reconciled.
Some of you would ask, why there's an ->each()
function for each Transaction, instead of just a bulk update? I assume there are Observers or other events that are automatically fired on the individual record update, that would not be automatically executed otherwise.
See the original code on GitHub.
Want More Code Examples?
So, these are just 5 examples. We've collected hundreds of code examples from Laravel open-source projects.
You can take a look at a few more DB transaction examples, or browse through all the list of code examples by topic/tag.
Thanks for sharing and learned new things.
Informative