I have a problem with this code :
foreach($commandes as $commande)
$conn = $this->em->getConnection();
$conn->beginTransaction();
$conn->setAutoCommit(false);
try {
$tmp = $this->checkCommande($commande); // in this function there is multiple relationship created and pushed
if ($tmp) {
$result = $tmp['result'];
if ('OK' === $result) {
$commande->setCompleted(true);
$this->em->flush();
$conn->commit();
}
}
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
}
If I have multiple commands and if one is not committed then all are not committed.
You have set a condition such that commands can only be committed if 'OK' === $result. If this condition is not met, an exception is thrown and the changes are rolled back. To fix this issue, you should move the $conn->commit() call outside of this condition, those changes are always committed unless an exception is thrown. The new code might look like this:
foreach($commandes as $commande)
{
$conn = $this->em->getConnection();
$conn->beginTransaction();
$conn->setAutoCommit(false);
try {
$tmp = $this->checkCommande($commande); // in this function there is multiple relationship created and pushed
if ($tmp) {
$result = $tmp['result'];
if ('OK' === $result) {
$commande->setCompleted(true);
$this->em->flush();
}
}
$conn->commit(); // outside this line
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
}
Related
I try this :
public function destroy($id)
{
DB::beginTransaction();
try {
$product = $this->product_repository->find($id);
$result = $product->categories()->detach();
if($result) {
list($status,$instance) = $this->product_repository->delete($id);
}
DB::commit();
return ['status'=>true,'data'=>$status];
} catch (\Exception $e) {
DB::rollback();
return ['status'=>false, 'message'=>$e->getMessage()];
}
}
If the code executed, $this->product_repository->delete($id) not work / not success delete.
But this : $product->categories()->detach();, it works / success deleted.
How to if delete product failed, delete category also failed?
You can't add return statement inside transaction that halts entire process and DB::rollback() is executed.
To switch the return, You can define a boolean variable and make false while you catch exception.
Like this:
public function destroy($id)
{
$success = true;
DB::beginTransaction();
try{
// Your Code
$product = $this->product_repository->find($id);
$result = $product->categories()->detach();
if($result) {
list($status,$instance) = $this->product_repository->delete($id);
}
DB::commit();
}catch(\Exception $e){
DB::rollback();
$success = false;
}
if($success){
// Return data for successful delete
}
else{
// Return data for unsuccessful delete
}
}
Hope you understand.
You can use it like this:
$returnResult = [];
DB::beginTransaction();
try {
...
DB::commit();
$returnResult['status'] = true;
$returnResult['data'] = $status;
} catch (...) {
...
DB::rollback();
$returnResult['status'] = true;
$returnResult['message'] = $e->getMessage();
}
return $returnResult;
I'm working on an api, it handles the requests which comes from clients, then gets the response from server(developed using codeigniter 3) and forwards that back to client.
But, in case of any database errors, like duplicate id, or null values, the model class cannot handle that error to display a proper error message. I've tried the try catch block but not succeeded yet.
Here's the model:
public function add() {
try {
$this->db->trans_start(FALSE);
$this->db->insert('users', $preparedData);
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
throw new Exception("Database error:");
return false;
}
return TRUE;
} catch (Exception $e) {
log_message('error: ',$e->getMessage());
return;
}
}
One thing to mention, I've set db_debug to FALSE.
Any help would be appreciated.
As for CI 3, below code gets database error code and error message. db_debug is set to FALSE.
public function add() {
try {
$this->db->trans_start(FALSE);
$this->db->insert('users', $preparedData);
$this->db->trans_complete();
// documentation at
// https://www.codeigniter.com/userguide3/database/queries.html#handling-errors
// says; "the error() method will return an array containing its code and message"
$db_error = $this->db->error();
if (!empty($db_error)) {
throw new Exception('Database error! Error Code [' . $db_error['code'] . '] Error: ' . $db_error['message']);
return false; // unreachable retrun statement !!!
}
return TRUE;
} catch (Exception $e) {
// this will not catch DB related errors. But it will include them, because this is more general.
log_message('error: ',$e->getMessage());
return;
}
}
Refer to documentation at https://www.codeigniter.com/userguide3/database/queries.html#handling-errors
saying
If you need to get the last error that has occurred, the error() method will return an array containing its code and message.
It is a bit incomplete in my opinion because it does not show error code and error message in the example code.
I just lost an hour trying to figure out why I can't get the error in my code. You have to check for an error after each statement! Working solution:
function insertUpdate($data) {
$order = $data->order;
$order_products = $data->order_products;
$this->db->trans_start();
$order->user_id = $this->session->user_id;
$error = "OK";
if (!$this->db->insert('_order', $order)) {
$error = $this->db->error()["message"];
}
$id = $this->db->insert_id();
foreach ($order_products as $row) {
$row->order_id = $id;
if (!$this->db->insert('_order_product', $row)) {
$error = $this->db->error()["message"];
break;
}
}
$order_code = substr(md5($id), 0, 6);
if (!$this->db->where('order_id', $id)) {
$error = $this->db->error()["message"];
}
if (!$this->db->update('_order', ["order_code" => $order_code])) {
$error = $this->db->error()["message"];
}
$this->db->trans_complete();
return [
'result' => $error, 'order_code' => $order_code
];
}
Suggestion in above code
Remove line $this->db->trans_complete();
If we see $this->db->error() after completing transaction it will be always empty
Remove semicolon - log_message('error :',$e->getMessage());
return;
public function add()
{
try {
$this->db->trans_start(FALSE);
$this->db->insert('users', $preparedData);
// documentation at
// https://www.codeigniter.com/userguide3/database/queries.html#handling-errors
// says; "the error() method will return an array containing its code and message"
$db_error = $this->db->error();
if (!empty($db_error)) {
throw new Exception('Database error! Error Code [' . $db_error['code'] . '] Error: ' . $db_error['message']);
return false; // unreachable return statement !!!`enter code here`
}
return TRUE;
} catch (Exception $e) {
// this will not catch DB related `enter code here`errors. But it will include them, because this is more general.
log_message('error ',$e->getMessage());
return;
}
}
I had 2 tables. driver and part_time_available in the same form, when I select driver type = parttime, it'll show part_time_available field(day, start_time, end_time).
How to make condition if user choose fulltime. it didn't store part_time_available field to database.
here's my savehandler code so far :
public function saveHandler(Request $request, $obj)
{
try {
DB::beginTransaction();
$obj->fill($request->all());
if (!$obj->save()) {
throw new ValidationException($obj->errors());
}
foreach($request->parttimeAvailabilities as $pta) {
\Log::info($pta);
if (empty($pta['id'])) {
$parttimeAvailability = new PartTimeAvailability();
}
else {
$parttimeAvailability = PartTimeAvailability::find($pta['id']);
}
$parttimeAvailability->driver()->associate($obj);
$pta['driver_id'] = isset($pta['driver_id']);
$parttimeAvailability->day = $pta['day'];
$parttimeAvailability->start_time = isset($pta['start_time']) ? $pta['start_time'] : '00:00:00';
$parttimeAvailability->end_time = isset($pta['end_time']) ? $pta['end_time'] : '00:00:00';
$parttimeAvailability->available = isset($pta['available']);
$parttimeAvailability->save();
};
$obj->save();
if (!$parttimeAvailability->save()) {
throw new ValidationException($parttimeAvailability->errors());
}
DB::commit();
return $this->sendSuccessResponse($request);
} catch (ValidationException $e) {
DB::rollback();
\Log::error($e->errors);
return $this->sendErrorResponse($request, $e->errors);
} catch (Exception $e) {
DB::rollback();
\Log::error($e->getMessage());
return $this->sendErrorResponse($request,'Unable to process. Please contact system Administrator');
}
}
I mean before running foreach, it needs to check it's parttime or not.
any idea ?
You can give a condition before the whole foreach loop. such as:
if($request->get('driver_type') != 'full_time'){
foreach loop
}
I need to rollback transaction and send correct response to the client if anything in the try block fails so I do it like that:
try {
$wf = $this->createWordForm($requestParams);
$wfl = $this->createWordFormLink($requestParams, $wf);
$wordList = $this->bindWordFormOrLinkToTextWord($requestParams, $wf, $wfl);
$db->commit();
} catch (Kohana_ORM_Validation_Exception $e) {
$exceptionHasOccured = TRUE;
return JsonResponse::ValidationFail($this->response, $e->errors());
} catch (Exception $e) {
$exceptionHasOccured = TRUE;
return JsonResponse::Error($this->response, $e->getMessage());
} finally {
if ($exceptionHasOccured) {
$db->rollback();
}
}
As you can see I'm using finally construction to rollback transaction. Is this correct approach?
You could catch all exceptions in one catch block, as long as your not specifically using the exception type for any purpose.
try {
$wf = $this->createWordForm($requestParams);
$wfl = $this->createWordFormLink($requestParams, $wf);
$wordList = $this->bindWordFormOrLinkToTextWord($requestParams, $wf, $wfl);
$db->commit();
} catch (Exception $e) {
$db->rollback();
if($e instanceof Kohana_ORM_Validation_Exception ) {
return JsonResponse::ValidationFail($this->response, $e->errors());
} else {
return JsonResponse::Error($this->response, $e->getMessage())
}
}
I have a collection of items to save to database, but I want the record to be inserted only if not exists.
I think the most effective way would be to filter the collection before saving. Can Doctrine do it automatically?
Or shall I get all id's of all items in the collection, then query the database for items which do not exists on the list of these id's, then in foreach remove all collection items which we do not need, and finally save collection?
Any better approach suggested?
This is the save function from the Doctrine_Collection class
public function save(Doctrine_Connection $conn = null, $processDiff = true)
{
if ($conn == null) {
$conn = $this->_table->getConnection();
}
try {
$conn->beginInternalTransaction();
$conn->transaction->addCollection($this);
if ($processDiff) {
$this->processDiff();
}
foreach ($this->getData() as $key => $record) {
$record->save($conn);
}
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
throw $e;
}
return $this;
}
I'm not sure where you are getting your collection from, or if you are manually building it, but you might want to try extending the Doctrine_Collection class and overloading the save function like this
<?php
class My_Collection extends Doctrine Collection
{
public function save(Doctrine_Connection $conn = null, $processDiff = true, $createOnly = true)
{
if ($conn == null) {
$conn = $this->_table->getConnection();
}
try {
$conn->beginInternalTransaction();
$conn->transaction->addCollection($this);
if ($processDiff) {
$this->processDiff();
}
foreach ($this->getData() as $key => $record) {
if($createOnly)
{
if ($record->exists())
{
$record->save($conn);
}
}else{
$record->save($conn);
}
}
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
throw $e;
}
return $this;
}
}
I don't know anything about Doctrine, but the MySQL REPLACE query does just what you want - updates existing rows & creates new rows if no primary key matches were found.