This is a very useful resource http://fideloper.com/laravel-database-transactions to read more, check out the comments too, there's example on how to use transactions in yours tests.
Do You Use Database Transactions For Your Eloquent Queries?
Hey,
I was listening to the latest Laravel Podcast, they mentioned Database Transactions (they were talking about testing) and it got me thinking:
- is it a good idea to wrap your Eloquent queries in a Database Transaction?
- Do you use Database Transactions in your own apps?
For your tests in Laravel 5.1, all you have to do is "use DatabaseTransactions" in your testcase. That trait is available out of the box.
Yeah, that's cool but my questions are more like outside of testing.
Example:
DB::beginTransaction();
$user = User::create($formData);
if (!$user)
{
DB::rollbackTransaction();
}
DB::commitTransaction();
No need for transactions if it's a single query. However, the link i posted on my first reply has some really good examples.
@Ruffles @RachidLaasri @JeffreyWay The most important and valuable part of that article, apart from testing, lies in the comment concerning transactions & events
@JarekTkaczyk in what way exactly do you find the comments concerning transactions & events valuable? In other words, what are your conclusions? Not to use transactions? Or to use transactions but then not use events?
I think in any important app where you run multiple queries, transactions are an absolute must. Otherwise your data integrity will slowly break down with each unfortunate error in the server.
@Ruffles no point if only one query is run.
The point of transaction is when you have to perform many queries like inserting a model and all its related models in one time.
Canonical example : a bank transaction. You decrease the balance on one account and increase it on another. You absolutely need to rollback everything if one of the query fails.
@Mattiman In that you should take it into account, especially if you'd like to rely on the eloquent events heavily. That said, you'd rather not want to make other models depend on them.
Eloquent is great and has many features that make your work quick and easy for simple tasks, and lets you take off rapidly, but everything comes with a price. For anything bigger depending on all this cool eloquent stuff is not the way - you either want to use another tool (read: doctrine or custom solution) or add another layer of control, that works according to your real-world needs.
Ok thanks for the insight @JarekTkaczyk. Good points.
@pmall Ok cool. What about a use case where the register form contains user info + profile info? You have to run 2 inserts in 2 different tables. Is this a good use case for a transaction? Is the rule 2 or more connected queries?
Yes it is a good use case as you don't want the user to be inserted and the profile to fail. If one fail you want the whole thing to rollback.
Try this one
DB::beginTransaction(); try { $project = Project::find($id); $project->users()->detach(); $project->delete(); DB::commit(); } catch (\Exception $ex) { DB::rollback(); return response()->json(['error' => $ex->getMessage()], 500); }
Hi,
I just run into the same thing. I run two create record models inside a transaction and try/catch block, like so:
DB::beginTransaction();
try
{
$one = new ModelOne;
$one->key = 'SST';
$one->title = 'Some Sample Title';
$one->save();
//create version record
$two = new ModelTwo;
$two->parent_id = $one->id;
$two->content = 'blah blah blah';
$two->status = 'draft';
$two->save();
DB::commit();
return response(['message'=>'ALL GOOD'], 200);
}
catch (Exception $e)
{
DB::rollBack();
return response(['message'=>'FAILURE'], 500);
}
to test the failure, inside of the model ModelTwo i defined incorrect table name like so:
protected $table = 'table_twooooooo'; // table_two is the correct table name
As expected the second query failed with unknown table. Surprisingly the first record was created in table_one. So unless I missed something here the transactions do not seem to work with models - and that is really bad!
Even if the code crashed on new ModelTwo() because of incorrect table name, the transaction should have not been commited.
Using PostgreSQL 11
Hi. i know it's very late to reply. Just thougt this would be useful for anyone encountering the same problem as you.
If transaction rollback doesn't work. Make sure that the db support transaction and it's enabled. Especially if you are using mysql, MyISAM doesn't support transaction so you need to make sure you use InnoDB on your mysql server and each of your tables are created with InnoDB engine.
I don't know much about PostgreSQL though, as i've only ever used 12+ and have no problem with transaction before.
@gemaup very good insight
I prefer the DB:transaction($callback) method:
$result = DB::transaction(function () {
return // ...
});
The DB::transaction returns whatever the callback returns.
I think it looks cleaner, but that's my personal preference. If there's an exception, it automatically rollbacks the transaction.
Also, I only use transactions on the highest layer of user interaction. I.e., controller, command, graphQL mutation, etc.
Otherwise, the code gets unnecessarily cluttered. And I don't think transactions belong to the business logic anyways.
Exceptions
As for the exceptions, I don't deal with them on the controller.
My controller code always look like this:
public function create(MyCustomFormRequest $request)
{
$model = DB::transaction(function () use ($request) {
return Model::createFromRequest($request);
});
return response($model, 200);
}
Instead of having exception-handling code, I create custom exceptions and define a render($request) method for them.
The Exception::render() method deals with the HTTP response.
Example:
class InvalidOrderException extends Exception
{
public function make()
{
return new self('Invalid order number');
}
public function render()
{
return response()->setStatusCode(422);
}
}
Other exceptions that may occur and reach the controller (besides the custom ones):
- Laravel exceptions with a render (ex. a failed
find()or a failed gate check): Laravel already has a properrender()set to them; - Exceptions without a
render(ex. libraries and other Laravel exceptions): I handle them on the high-level method called by the controller; - Unhandled exceptions: I let the crash report system (ex. Sentry) handle them for me. The client gets a "500 - Server Error", and I get an email warning me about it.
Because they are all handled, I don't deal with them in the controller. So, I never have to handle exceptions and transactions together.
Please or to participate in this conversation.