If you have multiple operations with Laravel Models, some Exception may happen in the middle of processing them. In those cases, Validation is not enough to prevent invalid data, and a clever way is to use DB Transactions. Let's see how to combine them nicely with try-catch blocks.
Transactions are a way to ensure that if something goes wrong, the database is rolled back to the state before the Transaction started. This is useful when you have to do multiple database operations and you want to ensure that if one fails, the others are not executed. To illustrate this, let's take a look at the following example:
public function import($data){ foreach ($data as $row) { $date = $this->importData($row); }} private function importData(array $row){ // Does the user import but can fail due to missing data}
As you can see, this code is meant to import users from a CSV file. And while that might work, it is not protecting you from malformed information. For example, if the CSV file has a row with a missing email, the import will fail and stop immediately. As a result, part of the users will be imported, instead of rolling back all the operation, or skipping just that invalid row.
Here comes the Transaction to the rescue. Let's see how we can use it to ensure that the database is rolled back to the state before the Transaction starts if something goes wrong.
use DB; // ... public function import($data){ foreach ($data as $row) { DB::transaction(function () use ($data) { $date = $this->importData($row); }); }} private function importData(array $row){ // Does the user import but can fail due to missing data}
Using Transaction will allow us to make sure that if something goes wrong, the database is rolled back for that specific row. If the import fails for a user, the other users will still be imported. But this brings an issue where we need to figure out which row did not import...
Using an exception handler can give you a few benefits:
And this is only speaking about the imports. But there are many more valuable things you can do with it at any time in your application. Treat it like a safety net that will prevent you from writing bad data into the database.
use DB; // ... public function import($data){ foreach ($data as $row) { DB::transaction(function () use ($data) { DB::beginTransaction(); try { $date = $this->importData($row); DB::commit(); } catch (Exception $e) { DB::rollBack(); // Log the error // Log the row that failed } }); }}
With the try-catch block in place and a Transaction surrounding the action - you can safely import the data and make sure that if something goes wrong, the database is rolled back to the state it was before the Transaction started.