Laravel 5.1: How to execute a block of transactions? - php

I have the follow relationships:
A Student has a "one-to-one" relationship with a Person. A Person has a "many-to-many" relationship with Address.
I want to persist the data: first create the Person, after create the Addresses and then create the Student.
But I want to rollback the transations if any error occur during the persistence in any of these tables. Ex.: If I save the Person and the Addresses, and the Student fails, I want to rollback everything.
How to handle this with Eloquent?
Thanks.

Laravel provides vary simple way to handle this kind of situation.
DB::transaction(function () {
//all your codes
});
From laravel Documentation :
To run a set of operations within a database transaction,
you may use the transaction method on the DB facade. If an exception is thrown within the transaction Closure, the transaction will automatically be rolled back. If the Closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the transaction method.
Also if you want to do it manually , you can do it using
DB::beginTransaction();
//your codes
DB::commit();
To learn more about transactions, read official document Here

Related

Laravel - On Catch Delete Created Object

So, I am submitting a form with 3 components, user -> company -> branch.
Right now, if I catch an error in any child object, I am unable to delete the parent records and when the user tries to resubmit the form he can't use the same details because they're already registed.
My question is:
Can I delete the objects created within try{} in catch{} ?
If not, what practice do you recommend to handle this type of situations?
Thanks in advance!
You can use transactions for that scenario.
Let's say you have multiple create/update/delete and if one of them fails you want it to undo the others, then transaction comes to the rescue.
DB::transaction(function () {
User::create([...]);
Company::create([...]);
Branch::create([...]);
});
So here if any one of the above statement fails, other statements are rolled back.
You can read in detail in the documentation.
https://laravel.com/docs/7.x/database#database-transactions

Laravel 5.2 upgrade causes sql lock error, doesn't respect database transactions

We were getting along just fine with some Eloquent code in Laravel 5.1:
DB::transaction(function () {
// Eloquent model saved here
$bla = new Bla();
$bla->blabla = 'bla';
$bla->save()
});
The Bla Eloquent model has a model observer class which checks for creates, updates, and deletes, and subsequently writes to a log table. In Laravel 5.1 this worked flawlessly but after upgrading to 5.2 it seems to forget it's in a transaction and throws this error because it actually attempts to write to the log table:
General error: 1205 Lock wait timeout exceeded; try restarting transaction
Mind you this also fails in 5.2 when we tried using model events. The event/observer tries to write immediately to the log table instead of when the transaction is committed. Is there a way to get observer/event classes to work properly inside a transaction in Laravel 5.2 or should I revert to Laravel 5.1? My current theory is that since the observer is one level removed from the transaction, something in the new code introduced a bug that no longer respects the transaction at this level.
This can happen if you are using multiple DB connections pointing to the same database and the observer/listener is on a different connection to the default one.
This happened to me and the fix was to make sure the observer was on the default (or same) connection.

Laravel 5.1 job works if immediate but fails if queued due to Eloquent model error

I have a Laravel job that I dispatch to read customer data from a file. The job has a logger model dependency, and this logger tracks certain stats then writes to a log db table when the job completes. When I dispatch this job immediately it works fine, but if I queue the exact same job with the exact same parameters and then run it from the queue it fails with the error:
[Illuminate\Database\Eloquent\ModelNotFoundException]
No query results for model [app\Models\MyModel].
I think this is due to the serialize/unserialize nature inherent in the Laravel queue system which breaks Eloquent, which tries the findOrFail() method when I am never actually trying to find something in the db. I am merely creating a new log from a blank model. Whatever the case, it is clear that the same Laravel job, when not queued, runs fine -- but it fails when queued. I am at a loss and wondering if there is a way to work around this issue.
Thanks.
I worked around it by instantiating and then saving the model object before passing it into the job's constructor, so it at least has a primary key set. This eliminates the findOrFail() method. A little hacky but it's the best I could come up with.
Make an error handler (put it in your routes)
App::error(function(Illuminate\Database\Eloquent\ModelNotFoundException $e)
{
return Response::make('Not found',404);
});

Call additional method on DB::update or DB::delete (laravel, eloquent)

I'm in the process of trying to create a logging interface for our application. I need to track when a change happens in the database through the application. So when someone updates a field I need to insert a row into the log with the table, columns original value, columns new value, timestamp, and user that made change. To me the logical way of doing this is to tie into the DB class in laravel so everytime it's called and an update / delete method is used it runs my new method of getting the needed info and inserting it in the log.
I need it to work at the DB level I believe as it needs to happen for updates / deletes called from DB or eloquent.
How would I go about doing this?
Eloquent provides you with some nice events, they're even in the docs ! Who knew you can find so much in there.
http://laravel.com/docs/eloquent#model-events
Instead of trying to attach something onto the DB layer, have you considered using Model Observers (http://laravel.com/docs/eloquent#model-observers) to watch all update and delete events and put your logic in that observer?
Upon searching and trial and error I accomplished my goal the following way.
I created an Event listener that I for organizational purposes placed in /app/events/database.php
With the following
<?php
Event::listen('illuminate.query', function($query, $bindings, $time, $name)
{
// Code to log query goes here
});
I then placed in my /app/start/global.php the following line
include(app_path().'/events/database.php');
This now captures all requests to query the database using either DB, or Eloquent.

Multiple Service Layers and Database Transactions

I'm just wondering how to best handle transactions across multiple service layers. The service layers use an ORM to store and retrieve from the database. Should the transactions be known and handled within the individual service layers? Or should they be handled by another layer?
For example: I have two service layers for users and clients. I would like to:
1) Create and save a new client
2) Create and save a new user
3) Assign that user to the client
All within a single transaction.
A simple example might look like this:
$userManagementService = new UserManagementService;
$newUserData = array(...);
$newUser = $userManagementService->create($newUserData);
$clientManagementService = new ClientManagementService;
$newClientData = array(...);
$newClient = $clientManagementService->create($newClientData);
$userManagementService->assignUserToClient($newUser, $newClient);
Where should transaction logic go?
Do not try to do nested transactions within service layers or within the ORM.
Transactions are global to the DB connection. Unless your RDBMS supports nested transactions natively and your DB API exposes nested transactions, you can run into anomalies.
For details, see my answer to How do detect that transaction has already been started?
Since you're using PHP, the scope of your transactions is at most a single request. So you should just use container-managed transactions, not service-layer transa. That is, start the transaction at the start of handling the request, and commit (or rollback) as you finish handling the request.
If an exception requiring a rollback occurs deep within nested ORM actions, then bubble that up by using an Exception, and let the container (i.e. your PHP action controller) take care of it.
Are you facing an aggregation of transactions? Does this pseudo code match what I think you're saying?
try
begin application transaction
begin ORM transaction 1
create new user
commit request
begin ORM transaction 2
create new client
commit request
begin ORM transaction 3
create user client association
commit request
commit application tx
catch()
abort ORM tx 3
abort ORM tx 2
abort ORM tx 1
abort app tx
At any point a rollback of a nested transaction will likely throw an exception, and those exceptions would logically roll back all the nested transactions in the two-phase commit.
I might not be getting what you're after tho.

Categories