I was curious if there is a way we can check if there is a constraint violation error when delete or insert a record into the database.
The exception thrown is called 'QueryException' but this can be a wide range of errors. Would be nice if we can check in the exception what the specific error is.
You are looking for the 23000 Error code (Integrity Constraint Violation). If you take a look at QueryException class, it extends from PDOException, so you can access to $errorInfo variable.
To catch this error, you may try:
try {
// ...
} catch (\Illuminate\Database\QueryException $e) {
var_dump($e->errorInfo);
}
// Example output from MySQL
array (size=3)
0 => string '23000' (length=5)
1 => int 1452
2 => string 'Cannot add or update a child row: a foreign key constraint fails (...)'
To be more specific (Duplicate entry, not null, add/update child row, delete parent row...), it depends on each DBMS:
PostgreSQL and SQL server follow the SQL standard's conventions for SQLSTATE code, so you may return the first value from the array $e->errorInfo[0] or call $e->getCode() directly
MySQL, MariaDB and SQLite do not strictly obey the rules, so you need to return the second value from the array $e->errorInfo[1]
For laravel, handling errors is easy, just add this code in your "app/start/global.php" file ( or create a service provider):
App::error(function(\Illuminate\Database\QueryException $exception)
{
$error = $exception->errorInfo;
// add your business logic
});
first put this in your controller
use Exception;
second handle the error by using try catch like this example
try{ //here trying to update email and phone in db which are unique values
DB::table('users')
->where('role_id',1)
->update($edit);
return redirect("admin/update_profile")
->with('update','update');
}catch(Exception $e){
//if email or phone exist before in db redirect with error messages
return redirect()->back()->with('phone_email','phone_email_exist before');
}
New updates here without need to use try catch you can easily do that in validation rules as the following code blew
public function update(Request $request, $id)
{
$profile = request()->all();
$rules = [
'name' => 'required|unique:users,id,'.$id,
'email' => 'required|email|unique:users,id,'.$id,
'phone' => 'required|unique:users,id,'.$id,
];
$validator = Validator::make($profile,$rules);
if ($validator->fails()){
return redirect()->back()->withInput($profile)->withErrors($validator);
}else{
if(!empty($profile['password'])){
$save['password'] = bcrypt($profile['password']);
}
$save['name'] = $profile['name'];
$save['email'] = $profile['email'];
$save['phone'] = $profile['phone'];
$save['remember_token'] = $profile['_token'];
$save['updated_at'] = Carbon::now();
DB::table('users')->where('id',$id)->update($save);
return redirect()->back()->with('update','update');
}
}
where id related to record which you edit.
You may also try
try {
...
} catch ( \Exception $e) {
var_dump($e->errorInfo );
}
then look for error code.
This catches all exception including QueryException
If you are using Laravel version 5 and want global exception handling of specific cases you should put your code in the report method of the /app/Exception/Handler.php file. Here is an example of how we do it in one of our micro services:
public function render($request, Exception $e)
{
$response = app()->make(\App\Support\Response::class);
$details = $this->details($e);
$shouldRenderHttp = $details['statusCode'] >= 500 && config('app.env') !== 'production';
if($shouldRenderHttp) {
return parent::render($request, $e);
}
return $response->setStatusCode($details['statusCode'])->withMessage($details['message']);
}
protected function details(Exception $e) : array
{
// We will give Error 500 if we cannot detect the error from the exception
$statusCode = 500;
$message = $e->getMessage();
if (method_exists($e, 'getStatusCode')) { // Not all Exceptions have a http status code
$statusCode = $e->getStatusCode();
}
if($e instanceof ModelNotFoundException) {
$statusCode = 404;
}
else if($e instanceof QueryException) {
$statusCode = 406;
$integrityConstraintViolation = 1451;
if ($e->errorInfo[1] == $integrityConstraintViolation) {
$message = "Cannot proceed with query, it is referenced by other records in the database.";
\Log::info($e->errorInfo[2]);
}
else {
$message = 'Could not execute query: ' . $e->errorInfo[2];
\Log::error($message);
}
}
elseif ($e instanceof NotFoundHttpException) {
$message = "Url does not exist.";
}
return compact('statusCode', 'message');
}
The Response class we use is a simple wrapper of Symfony\Component\HttpFoundation\Response as HttpResponse which returns HTTP responses in a way that better suits us.
Have a look at the documentation, it is straightforward.
You can add the following code in app/start/global.php file in order to print the exception
App::error(function(QueryException $exception)
{
print_r($exception->getMessage());
});
check this part in the documentation
Related
Shortly: how can you set a specific http error code, instead of a generic 500, when a constraint fails on entity save?
Details
I'm using Symfony custom constraint #UniqueEntity (http://symfony.com/doc/current/reference/constraints/UniqueEntity.html) to assert that some data is not duplicated when saving an entity.
If this constraint check results in a violation, I get a 500 http code, while others may be more appropriate, e.g. 409 - Conflict (https://httpstatuses.com/409).
I can't seem to find any documentation on how to override the validation response.
Thank you in advance for any suggestion.
Maybe you could create a Listener to the event : kernel.exception
And then you will have something like :
<?php
public function onKernelException(GetResponseForExceptionEvent $event)
{
$e = $event->getException();
if ($e instanceof NameOfTheException) {
// logic here
return (new Response())
->setStatusCode(409)
;
}
}
Just catch exception in controller:
public function saveAction()
{
try {
$entity = new Entity('duplicate name');
$this->entityManager->persist($entity);
$this->entityManager->flush();
return new Response();
} catch(UniqueConstraintViolationException $e) {
return new Response('Entity with same name already exists', Response::HTTP_CONFLICT);
} catch (\Exception $e) {
return new Response('Internal error', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
I'm getting into troubles to capture and retrieve the MySQL Error codes and messages using Laravel 5.2 through the DB Facade with the select method.
What I want to do is to select/call a MySQL function that inserts a simple row in a table with a parameter. However what if the parameter in empty for a column that can't be null? MySQL throws an error (Error Code: 1048).
The problem is that I'm not getting any error/exception in Laravel in those situations and I can't know if the insert succeeded or not to handle the error in the proper way.
Here is my code:
DB::beginTransaction();
try{
$statement = "select mySQLFunction('".$param."')";
DB::select($statement);
DB::commit();
}catch(\Exception $e){
DB::rollback();
Log::error('Exception: '.$e->getMessage());
}
I wonder if there is a way to capture this kind of error codes and messages so Laravel application can know there was an error in MySQL database. Also to get the return value of the MySQL function in case of success.
Thanks in advance!
Laravel uses PDO, so you can use the errorInfo variable which returns the SQLSTATE error and the message. Basically, you need to use $e->errorInfo;
If you want to log all SQL errors into database, you can use the Exception Handler (app/Exceptions/Handler.php and listen for QueryExceptions. Something like this:
public function render($request, Exception $e)
{
switch ($e) {
case ($e instanceof \Illuminate\Database\QueryException):
LogTracker::saveSqlError($e);
break;
default:
LogTracker::saveError($e, $e->getCode());
}
return parent::render($request, $e);
}
Then you can use something like this:
public function saveSqlError($exception)
{
$sql = $exception->getSql();
$bindings = $exception->getBindings()
// Process the query's SQL and parameters and create the exact query
foreach ($bindings as $i => $binding) {
if ($binding instanceof \DateTime) {
$bindings[$i] = $binding->format('\'Y-m-d H:i:s\'');
} else {
if (is_string($binding)) {
$bindings[$i] = "'$binding'";
}
}
}
$query = str_replace(array('%', '?'), array('%%', '%s'), $sql);
$query = vsprintf($query, $bindings);
// Here's the part you need
$errorInfo = $exception->errorInfo;
$data = [
'sql' => $query,
'message' => isset($errorInfo[2]) ? $errorInfo[2] : '',
'sql_state' => $errorInfo[0],
'error_code' => $errorInfo[1]
];
// Now store the error into database, if you want..
// ....
}
I am trying to identify when inserting a record using eloquent in Laravel when it throws an exception because of a unique field error.
The code I have so far is:
try {
$result = Emailreminder::create(array(
'user_id' => Auth::user()->id,
'email' => $newEmail,
'token' => $token,
));
} catch (Illuminate\Database\QueryException $e) {
return $e;
}
It throws an exception OK I just don't know what to do to identify it as a column duplicate error?
Thanks,
Gavin.
I'm assuming you use MySQL, it's probably different for other systems
Okay first, the error code for duplicate entry is 1062. And here's how you retrieve the error code from the exception:
catch (Illuminate\Database\QueryException $e){
$errorCode = $e->errorInfo[1];
if($errorCode == 1062){
// houston, we have a duplicate entry problem
}
}
add this code inside class Handler (exception)
if($e instanceof QueryException){
$errorCode = $e->errorInfo[1];
switch ($errorCode) {
case 1062://code dublicate entry
return response([
'errors'=>'Duplicate Entry'
],Response::HTTP_NOT_FOUND);
break;
case 1364:// you can handel any auther error
return response([
'errors'=>$e->getMessage()
],Response::HTTP_NOT_FOUND);
break;
}
}
...
return parent::render($request, $exception);
The long (a little bit weird) way, but works for any Databases
You can use Doctrine's ExceptionConverterInterface
You should install package doctrine/dbal
And find the implementation of this interface by calling
app('db.connection')->getDoctrineConnection()->getDriver()->getExceptionConverter()
or
app(\Illuminate\Database\DatabaseManager::class)->connection()->getDoctrineConnection()->getDriver()->getExceptionConverter()
It applies Doctrine\DBAL\Driver\Exception as a first argument
You can get internal Doctrine\DBAL\Driver\PDO\Exception which implements this interface and instantiate it from your PODException
You can get your PDOException from QueryException this way:
$previous = $queryException->getPrevious();
if ($previous && ($previous instance \PDOException)) {
// ...
}
So, the final solution looks like:
$exceptionConverter = app('db.connection')->getDoctrineConnection()->getDriver()->getExceptionConverter()
$previous = $queryException->getPrevious();
if ($previous && ($previous instance \PDOException)) {
$driverException = $exceptionConverter->convert($previous, null);
if ($driverException instanceof \Doctrine\DBAL\Exception\UniqueConstraintViolationException) {
// Our Exception is about non-unique value
}
}
Please do not use this code in production :)
We all use DB::transaction() for multiple insert queries. In doing so, should a try...catch be placed inside it or wrapping it? Is it even necessary to include a try...catch when a transaction will automatically fail if something goes wrong?
Sample try...catch wrapping a transaction:
// try...catch
try {
// Transaction
$exception = DB::transaction(function() {
// Do your SQL here
});
if(is_null($exception)) {
return true;
} else {
throw new Exception;
}
}
catch(Exception $e) {
return false;
}
The opposite, a DB::transaction() wrapping a try...catch:
// Transaction
$exception = DB::transaction(function() {
// try...catch
try {
// Do your SQL here
}
catch(Exception $e) {
return $e;
}
});
return is_null($exception) ? true : false;
Or simply a transaction w/o a try...catch
// Transaction only
$exception = DB::transaction(function() {
// Do your SQL here
});
return is_null($exception) ? true : false;
In the case you need to manually 'exit' a transaction through code (be it through an exception or simply checking an error state) you shouldn't use DB::transaction() but instead wrap your code in DB::beginTransaction and DB::commit/DB::rollback():
DB::beginTransaction();
try {
DB::insert(...);
DB::insert(...);
DB::insert(...);
DB::commit();
// all good
} catch (\Exception $e) {
DB::rollback();
// something went wrong
}
See the transaction docs.
If you use PHP7, use Throwable in catch for catching user exceptions and fatal errors.
For example:
DB::beginTransaction();
try {
DB::insert(...);
DB::commit();
} catch (\Throwable $e) {
DB::rollback();
throw $e;
}
If your code must be compartable with PHP5, use Exception and Throwable:
DB::beginTransaction();
try {
DB::insert(...);
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw $e;
} catch (\Throwable $e) {
DB::rollback();
throw $e;
}
You could wrapping the transaction over try..catch or even reverse them,
here my example code I used to in laravel 5,, if you look deep inside DB:transaction() in Illuminate\Database\Connection that the same like you write manual transaction.
Laravel Transaction
public function transaction(Closure $callback)
{
$this->beginTransaction();
try {
$result = $callback($this);
$this->commit();
}
catch (Exception $e) {
$this->rollBack();
throw $e;
} catch (Throwable $e) {
$this->rollBack();
throw $e;
}
return $result;
}
so you could write your code like this, and handle your exception like throw message back into your form via flash or redirect to another page. REMEMBER return inside closure is returned in transaction() so if you return redirect()->back() it won't redirect immediately, because the it returned at variable which handle the transaction.
Wrap Transaction
try {
$result = DB::transaction(function () use ($request, $message) {
// execute query 1
// execute query 2
// ..
});
// redirect the page
return redirect(route('account.article'));
} catch (\Exception $e) {
return redirect()->back()->withErrors(['error' => $e->getMessage()]);
}
then the alternative is throw boolean variable and handle redirect outside transaction function or if your need to retrieve why transaction failed you can get it from $e->getMessage() inside catch(Exception $e){...}
I've decided to give an answer to this question because I think it can be solved using a simpler syntax than the convoluted try-catch block. The Laravel documentation is pretty brief on this subject.
Instead of using try-catch, you can just use the DB::transaction(){...} wrapper like this:
// MyController.php
public function store(Request $request) {
return DB::transaction(function() use ($request) {
$user = User::create([
'username' => $request->post('username')
]);
// Add some sort of "log" record for the sake of transaction:
$log = Log::create([
'message' => 'User Foobar created'
]);
// Lets add some custom validation that will prohibit the transaction:
if($user->id > 1) {
throw AnyException('Please rollback this transaction');
}
return response()->json(['message' => 'User saved!']);
});
};
You should see that in this setup the User and the Log record cannot exist without eachother.
Some notes on the implementation above:
Make sure to return anything the transaction, so that you can use the response() you return within its callback as the response of the controller.
Make sure to throw an exception if you want the transaction to be rollbacked (or have a nested function that throws the exception for you automatically, like any SQL exception from within Eloquent).
The id, updated_at, created_at and any other fields are AVAILABLE AFTER CREATION for the $user object (for the duration of this transaction at least). The transaction will run through any of the creation logic you have. HOWEVER, the whole record is discarded when SomeCustomException is thrown. An auto-increment column for id does get incremented though on failed transactions.
Tested on Laravel 5.8
I'm using Laravel 8 and you should wrap the transaction in a try-catch as follows:
try {
DB::transaction(function () {
// Perform your queries here using the models or DB facade
});
}
catch (\Throwable $e) {
// Do something with your exception
}
in laravel 8, you can use DB::transaction in try-catch.
for example :
try{
DB::transaction(function() {
// do anything
});
}
catch(){
// do anything
}
if each of query be failed on try, the catch block be run.
First: using PostgreSQL database in Laravel makes things more tricky.
If you don't rollback after a transaction error, each futher queries will throw this error In failed sql transaction: ERROR: current transaction is aborted, commands ignored until end of transaction block. So if you can't save original error message in a table BEFORE the rollback.
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
$user2 = User::find(2); // ko, "In failed sql transaction" error
$user2->update(['field' => 'value']);
}
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception $exception) {
DB::rollBack();
$user2 = User::find(2); // ok, go on
$user2->update(['field' => 'value']);
}
Second: pay attention to Eloquent model attributes system.
Eloquent model keeps changed attributes after an update error, so if we want to update that model inside the catch block, we need to discard bad attributes. This isn't a dbtransaction affair, so the rollback command is useless.
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
DB::rollBack();
$user1->update(['success' => 'false']); // ko, bad update again
}
try {
DB::beginTransaction(); //start transaction
$user1 = User::find(1);
$user1->update(['money' => 'not_a_number']); //bad update
}
catch(Exception|Error $exception) {
DB::rollBack();
$user1->discardChanges(); // remove attribute changes from model
$user1->update(['success' => 'false']); // ok, go on
}
I have a large data set that needs to be written to the database when posted to the server, but it's possible that a bug in the client editor is added extra records that trigger an "integrity constraint violation".
My problem is that when I reach the point in the data where the error is trigger, then a lot of the previous data has already been updated in the database. I need to rollback and reject the posted data.
Here's my controller's action.
/**
* Handles saving data from the network editor.
*/
public function json_save()
{
if($this->request->is('post'))
{
$result = array();
$data = $this->request->input('json_decode');
if(isset($data->network_id) && !empty($data->network_id))
{
$dataSource = $this->Node->getDataSource();
$dataSource->begin();
$result['nodes'] = $this->updateModel($data->network_id, $this->Node, 'nodes', $data, array(
'ParamValue'
));
$result['connections'] = $this->updateModel($data->network_id, $this->Connection, 'connections', $data);
$dataSource->commit();
$this->viewClass = 'Json';
$this->set('result', $result);
$this->set('_serialize', array(
'result'
));
return;
}
}
throw new ErrorException('Posted data missing.');
}
My controller's updateModel function performs a few deletes and updates to the models $this->Node and $this->Connection.
How do I roll back upon an "integrity constraint violation" which is usually thrown during updating of the $this->Connection model.
I'm not sure if it's a PHP exception that I can catch and then do a rollback, or if there is a different way to catch it.
You can do a :
$dataSource->begin();
try {
// The queries here.
} catch (Exception $e) {
$dataSource->rollback();
throw $e;
}
$dataSource->commit();