Catching a query exception? - php

I have a slug column in my database, it's unique.
If I try and store another row with a non unique slug I get a QueryException error.
I catch the error, and hope to return an error message something along the lines that "slug exists".
try {
User::create($data);
} catch (\Illuminate\Database\QueryException $e) {
//return the error
}
The above is fine, but I'm just wondering, what if another QueryException is thrown, not to do with the duplicate slug and i return a duplicate slug error message incorrectly.
Is there a way to find out what the query exception was and return an error message based on this? i know the exception provides its own message but I was hoping for something a little more user friendly.

To check if you got a QueryException because of a duplicate, check if $e->getCode() equals to 23000.
When a duplicate occurs due to a constraint, MySQL will issue signal SQLSTATE 23000 which you can use to determine what exactly went wrong. You can implement your own signal in MySQL to signal about various errors (via triggers etc)
Example:
try {
User::create($data);
} catch (\Illuminate\Database\QueryException $e) {
if($e->getCode() === '23000') {
// you got the duplicate
}
}

You can check errorInfo of the exception object
It looks like this..
+errorInfo: array:3 [▼
0 => "23000"
1 => 1062
2 => "Duplicate entry 'abc#gmail.com' for key 'users_email_unique'"
You can write following code in catch..
try{}
catch(QueryException $qe){
If ($qe->errorInfo[0] == "23000" && $qe->errorInfo[1] == "1062"){
return "Your message"
}else{
return "General Message"
}
}

Related

How to create an error page to a QueryException?

I'm working on a commercial application and I've created Blade files for several kinds of HTTP errors. They are placed in /resources/views/errors and are working fine for authorization (503.blade.php), page not found (404.blade.php), and so on.
I've created files for 400, 403, 404, 500 and 503, until now.
The issue is when a QueryException is thrown. In this case, "Whoops, looks like something went wrong." appears.
For example, considering that name cannot be null, when I do something like this, Laravel throws a QueryException:
User::create([
'name' => null,
'email' => 'some#email.com'
]);
The exception would be:
QueryException in Connection.php line 651: SQLSTATE[23000]: Integrity
constraint violation: 1048 Column 'name' cannot be null (SQL: insert
into users (nome, email, updated_at, created_at) values (,
some#email.com, 2018-02-09 12:10:50, 2018-02-09 12:10:50))
I don't want "Whoops, looks like something went wrong." to appear to the end user, I want to show a custom page. What kind of error file do I need to create to achieve this behavior?
Try this in your controller:
try {
User::create([
'name' => null,
'email' => 'some#email.com'
]);
} catch ( \Illuminate\Database\QueryException $e) {
// show custom view
//Or
dump($e->errorInfo);
}
To catch all Query Exceptions:
You need to customize the render() method of App\Exceptions\Handler, as stated in the Docs.
public function render($request, Exception $e)
{
if ( $e instanceof \Illuminate\Database\QueryException ) {
// show custom view
//Or
dump($e->errorInfo);
}
return parent::render($request, $exception);
}

Doctrine Exception: Deadlock found when trying to get lock

I have a Symfony app which exposes a collection of JSON web services used by a mobile app.
On the last few days we are having many concurrent users using the app (~5000 accesses per day) and a Doctrine error started to "randomly" appear in my logs. It appears about 2-3 times per day and this is the error:
Uncaught PHP Exception Doctrine\DBAL\Exception\DriverException: "An exception occurred while executing 'UPDATE fos_user_user SET current_crystals = ?, max_crystals = ?, updated_at = ? WHERE id = ?' with params [31, 34, "2017-12-19 09:31:18", 807]:
SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction" at /var/www/html/rollinz_cms/releases/98/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php line 115
It seems it cannot get the lock while updating the users table. The controller code is the following:
/**
* #Rest\Post("/api/badges/{id}/achieve", name="api_achieve_badge")
*/
public function achieveAction(Badge $badge = null)
{
if (!$badge) {
throw new NotFoundHttpException('Badge not found.');
}
$user = $this->getUser();
$em = $this->getDoctrine()->getManager();
$userBadge = $em->getRepository('AppBundle:UserBadge')->findBy(array(
'user' => $user,
'badge' => $badge,
));
if ($userBadge) {
throw new BadRequestHttpException('Badge already achieved.');
}
$userBadge = new UserBadge();
$userBadge
->setUser($user)
->setBadge($badge)
->setAchievedAt(new \DateTime())
;
$em->persist($userBadge);
// sets the rewards
$user->addCrystals($badge->getCrystals());
$em->flush();
return new ApiResponse(ApiResponse::STATUS_SUCCESS, array(
'current_crystals' => $user->getCurrentCrystals(),
'max_crystals' => $user->getMaxCrystals(),
));
}
I looked into MySQL and Doctrine documentation but I couldn't find a reliable solution. Doctrine suggests retrying the transaction but it doesn't show an actual example:
https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-example.html
try {
// process stuff
} catch (\Doctrine\DBAL\Exception\RetryableException $e) {
// retry the processing
}
This posts suggests retrying the transaction. How can I do it?
Could it be a server problem (too many accesses) and I must boost the server or the code is wrong and I must explicitly handle the deadlock in my code?
This is a MySQL issue. Multiple simultaneous transactions blocking the same resources.
Check if you have cronjobs that may block the records for long times.
Otherwise is just concurrent requests updating the same data, you may have better knowledge where this data gets updated.
Dirty attempt for a retry in php:
$retry=0;
while (true) {
try {
// some more code
$em->flush();
return new ApiResponse(ApiResponse::STATUS_SUCCESS, array(
'current_crystals' => $user->getCurrentCrystals(),
'max_crystals' => $user->getMaxCrystals(),
));
} catch (DriverException $e) {
$retry++;
if($retry>3) { throw $e; }
sleep(1); //optional
}
}
Albert's solution is the right one but you also must recreate a new EntityManager in the catch clause using resetManager() of your ManagerRegistry. You'll get exceptions if you continue to use the old EntityManager and its behavior will be unpredictable. Beware of the references to the old EntityManager too.
This issue will be hopefully corrected in Doctrine 3: See issue
Until then, here is my suggestion to handle the problem nicely: Custom EntityManager

Laravel Eloquent Catch Special Expection

I'm currently trying the following: In my db, I have a column called templateURL, which is a unique key, so it may only exist once. Then if something tries to submit a form, where this is the same as already exist an error is thrown, saying
Integrity constraint violation: 1062 Duplicate entry for key 'templateURL'.
Of course it does, that's why I made the column unique. But: What I want is not the default Laravel Error, but getting back to the form with the values still entered and a message (alert from bootstrap for example or just a div next to the input field) saying that this already exist and asking to chooose another one. So I need to catch this exception and do something custom instead. How can I achieve what I want?
You've to wrap your database operation into a try-catch block and catch the error and do something else when you get a error, maybe redirect with old input data with a error message.
The error code for duplicate entry is 1062. So if you get 1062 as error code that means this data is a duplicate entry. Here is the code look like for catching this exception.
try {
$data = Model::create(array(
'templateURL' => 'some value ',
));
} catch (Illuminate\Database\QueryException $e) {
$errorCode = $e->errorInfo[1];
if($errorCode == 1062){
// we have a duplicate entry problem
}
}
Or if you wish not to handle the exception yourself, you can take help of Laravel Validator. Like following in your controller
// validation rules
$rules = array(
'templateURL' => 'unique:YourTableNameHere'
);
$validator = Validator::make(Input::all(), $rules);
// check if the validation failed
if ($validator->fails()) {
// get the error messages from the validator
$messages = $validator->messages();
// redirect user back to the form with the errors from the validator
return Redirect::to('form')
->withErrors($validator);
} else {
// validation successful
$data = Model::create(array(
'templateURL' => 'some value ',
));
}
Then in your view template you can access the error messages via $errors variable.
Learn more about validation there https://laravel.com/docs/5.3/validation

Magento 1.6.2 SOAP API Errors: Using salesOrderShipmentCreate and continue to get DB integrity violations

I am trying to integrate with the Magento API to create shipments from a fulfillment center's csv file. I continue to get this error:
Caught create exception: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '100000381' for key 2
It seems that there is no way around it. Here is the PHP code:
<?php
$order = '100000636';
$carrier = 'fedex';
$proxy = new SoapClient($host.'api/v2_soap/?wsdl');
$sessionId = $proxy->login($user, $pass);
// Create new shipment
try {
$newShipmentId = $proxy->salesOrderShipmentCreate($sessionId, $order, array('13', '1'), 'shipment comment',false,false);
} catch (Exception $e) {
echo 'Caught create exception: ' .$e->getMessage();
}
var_dump($newShipmentId);
Check in the eav_entity_store table, if the value of increment_last_id is smaller than the actual increment ID that was used for the latest successfully created shipment. If so, try to replace it with the actual last value. Knowing that :
entity_type_id should by default be 8 for shipments, else use the value corresponding to shipment in the eav_entity_type table,
If you don't run in single store mode, check the value for the relevant store_id.

Drupal 7 db_update - checking for errors

I have a custom Drupal 7 module with an 'edit' page.
The form fields reference a couple of database tables, so to process the form, we attempt to update the first table, and we try to set a '$error' to 'true' and check against $error before we attempt to update the next table. For example:
HTML:
<input name="field1" />
<input name="field2" />
PHP:
$error = false;
$update_table_1 = db_update('table1')
->fields(array(
'field1' => $_POST['field1'],
))
->condition('id', $id)
->execute();
if(!update_table_1) {
$error = true;
}
if(!$error) {
$update_table_2 = db_update('table2')
->fields(array(
'field2' => $_POST['field2'],
))
->condition('id', $id)
->execute();
if(!$update_table_2) {
$error = true;
}
}
Problem: If only updating something in table 2, it will throw an error before it event gets to update table 2 because db_query says that it is not true since the field was the same as what was in the database (no change). Really, I only want to stop it if there was a database / code error.
Does the Drupal 7 db_update API have some kind of error reporting function like mysql_error()? Other suggestions?
The safest way you can do it is with a transaction and proper PHP error checking:
$transaction = db_transaction();
try {
// Query 1
db_update(...);
// Query 2
db_update(...);
}
catch (Exception $e) {
// Rollback the transaction
$transaction->rollback();
// Do something with the exception (inform user, etc)
}
I should mention the transaction is only necessary if you don't want the changes from the first query to persist if the second query fails. It's quite a common requirement but might not fit your use case.

Categories