Foreign key and transaction - php

I'm trying to use transaction when creating table group, and table with relation user-group.
It works ok when I don't use transaction, so the naming of the attributes is correct. Here is the code:
$db = Yii::app()->db;
$transaction = $db->beginTransaction();
try {
$model->attributes=$_POST['MyGroup'];
$model->save();
$model->refresh();
$userMyGroup = new UserMyGroup();
$userMyGroup->IDMyGroup = $model->IDMyGroup;
$userMyGroup->IDUser = Yii::app()->user->id;
$userMyGroup->save();
$transaction->commit();
} catch (CDbException $ex) {
Yii::log("Couldn't create group:".$ex->errorInfo[1], CLogger::LEVEL_ERROR);
$transaction->rollback();
}
The error is:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_UserMyGroup_MyGroup". The conflict occurred in database "MyDatabase", table "dbo.MyGroup", column 'IDMyGroup'.. The SQL statement executed was: INSERT INTO [dbo].[UserMyGroup] ([IDMyGroup], [IDUser]) VALUES (:yp0, :yp1). Bound with :yp0=4022, :yp1=1
Problem is probably that the saved model might not be in database while saving the second model(userMyGroup) with the foreign key. How to do the transaction correctly?
EDIT:
I've found out that the problem is caused by audit module, it is trying to log the query, but can't as it is in transaction and not really saved yet in database. I'm trying to figure out how to use this transaction along with the module...

The refresh method repopulates active record with the latest data.
While transaction is not commited latest data is existing data in table.
Move $model->refresh(); after $transaction->commit();

I've found out that the problem is caused by audit module which I'm using, it is trying to log the query, but can't as it is in transaction and not really saved yet in database. Unfortunately, I didn't figure out how to use this transaction along with the module, so the result is to disable audit module on the classes used in transaction.

Related

Laravel Transactions Not working when this condition happenes

Let me start with my code
On my Controller file this is the code
namespace Something\Somewhere\Controller{
class Mobile extends Controller{
public function saveMobilesIntoDb(Request $request,MediaManager $manager){
$requestMobileData = $request->all();
DB::beginTransaction();
try{
/*Do something*/
..
..
$bigMediaArray = $manager->mobileImagesManager($media,$insertedMobile,$mediaSlug);
..
..
DB::commit();
}catch (\Exception $exception) {
DB::rollback();
dump($exception);
}
}
}
}
Notice I am using a service there, The service is nothing but a namespace to manage the code in the service class this what happening
namespace Something\Somewhere\Service{
class MediaManager{
public function mobileImagesManager($media, $mobileId, $slug){
//Do some stuff
///Create folder
return array;
}
}
Now the issue is when I get some error in the service and I resend the data then suppose the last id inserted into database was 5 and then the error came but didn't rolled back so it saved the new id with 7 . and I don't want this to happen. I know the rollback is not working when I am not in the scope but what I tried so far is
I wrapped the service function into try-catch and in the catch I used the DB::rollback() but it didn't helped.
Please let me know how do I solve it and rollback everything when I am not in the scope.
Thank you for you time
As Alex said, due to Mysql official docs, the auto incremented ID will not rollback after transaction failure.
In all lock modes (0, 1, and 2), if a
transaction that generated
auto-increment values rolls back,
those auto-increment values are
“lost.” Once a value is generated for
an auto-increment column, it cannot be
rolled back, whether or not the
“INSERT-like” statement is completed,
and whether or not the containing
transaction is rolled back. Such lost
values are not reused. Thus, there may
be gaps in the values stored in an
AUTO_INCREMENT column of a table.

Lavavel/Eloquent 5 transactions rolling back not backing save

I do seem to be having problems getting database transactions to work on a model. I've referred to related posts on SO, but no luck yet.
In my example, I create a new record in the DB. I should be able to rollback and the new record should have disappeared shouldn't it?
try{
DB::beginTransaction();
$oNewMap = $oMap->replicate();
$oNewMap->name = "[test] " . $oNewMap->name;
$oNewMap->save(); // works
DB::rollBack(); / /record still in db
}
catch(\Exception $e){
DB::rollBack();
/* Transaction failed. */
}
When the rollback occured, why wouldn't the saved record disappear from the DB? Am I missing something with how models work with transactions?
The physical tables are all InnoDB, btw.
[EDITTED: to simplify the problem to a simple save and rollback, not doing two saves where the second save violates an FK constraint.]
If the model doesn't use the default database connection, you have to specify it on the transaction:
DB::connection('name')->beginTransaction();
DB::connection('name')->commit();
DB::connection('name')->rollBack();
Seeing your question, i remember a long time ago, where I had the same problem.
In the end i found out, that the function is called rollBack and not rollback - Note the capitalized B
Check your transaction level and conform that you are working with single beginTransaction() so, might you will find solution perfectly.
DB::beginTransaction()
DB::beginTransaction()
DB::transactionLevel() // will return 2
DB::commit() // doesn't commit
DB::transactionLevel() // will return 1
DB::commit() // finally, it commits to the database
DB::transactionLevel() // will return 0

Laravel duplicate entry after force delete

Working on a Laravel 4.2 app (yes, it's an older app not worth upgrading)
Trying handle the case where a user was soft deleted and wants to create an account again.
Having the issue that registering a new user doesn't work as the 'email' column is unique and the soft deleted record still exists.
I would much rather prefer to create a new user record (with all other fields empty), with same incrementing ID to keep relations with other soft deleted records.
So physically delete the old record and create a new one with the old ID? Right? Not working.
Calling forceDelete() on the record works, it does remove it from the Database, but then trying to create a new record immediately afterward throws a 'duplicate key' exception. Refreshing the page it is no longer a problem, as it doesn't see the old record.
It seems that the error is coming from MYSQL itself, though php should be waiting for a response to the delete query before executing the insert query, so mysql should know at this point that the record has been removed.
Is it possible that this error is actually coming from some sort of memory cache in Laravel?
I am also using the Sentinel authentication package, if that is of relevance.
Here is the basic code:
$found = User::where('email', $email)->first();
if ($found->deleted_at) {
$old_id = $found->id;
$found->forceDelete();
$found->save();
$unique_id = $this->generateUnique(NULL, $first_name . $last_name);
$user = Sentinel::registerAndActivate(array(
'id' => $old_id,
'email' => $email,
'password' => $password,
'first_name' => $first_name,
'last_name' => $last_name,
'unique_id' => $unique_id,
'preferred_lang' => $this->locale,
));
}
This results in a
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'whoever#mail.com' for key 'users_email_unique'
Does anyone know why this is? Is there an easy solution?
From the docs (https://laravel.com/docs/4.2/eloquent):
To restore a soft deleted model into an active state, use the restore method:
$user->restore();
To determine if a given model instance has been soft deleted, you may use the trashed method:
if ($user->trashed())
{
//
}
I actually got my code to work...
I read somewhere that I had to call
$found->save()
to update the deleted record, but that still had not worked for me.
Oddly enough, switching to
$found->update()
worked.
You can restore deleted row like this:
$deletedRow = User::withTrashed()->where('email', $email)->first();
if($deletedRow) {
$deletedRow->restore();
}

Update object instead of inserting with Doctrine using assignIdentifier()

I'm pretty new to Doctrine, but as I understand it, the assignIdentifier() method is supposed to tell Doctrine to update the relevant row into the database instead of inserting a new one.
I have an object that I'm building through a workflow, so the identifier has an id of null until I call $object->save(); which inserts it, and this does work.
If however I call $object->assignIdentifier($newobj->id); and then $object->save(); it does nothing - it does not insert a new row and does not update the old one.
If a certain condition is true, I want to pull a different record out of the DB and update that row instead of inserting the new one.
Am I understanding something wrong here?
Some code to illustrate:
if($this->object->payments > 0) {
$older_payment = Doctrine_Query::create()
->from('OldPaid p')
->where('p.dealid = ?', $this->object->transid)
->fetchOne()
;
$this->object->assignIdentifier($older_payment->id);
}
$this->object->save();
Like i got to know, save() will not update an existing record with autoincrement on ID.
I have the same problem using doctrine 1.2.
an idea i have use this one, the only workaroung i found:
$query = Doctrine_Query::create()->update('OldPaid');
$query->set($yourFieldname, '?', $yourValue);
$query->addwhere('p.dealid = ?', $this->object->transid);
$query->execute();
Thiw will function when a record is in the DN with the primaryKey dealid = $this->object->transid.
greeting m
Usually, if you retrieve a record, you can update it with the save() method. Doctrine recognizes this (since the PK doesn't change) and updates the record.
From the docs:
Updating objects is very easy, you
just call the Doctrine_Record::save()
method
Another way can be replace(), but I usually use just save() and does either the saving or the updating if the record already exists.
As far as I can read from the description of assignIdentifier() never used it myself) it will only work with retrieving an object by its ID, so updating something with this method will not work.

Handling foreign key exceptions in PHP

What is the best way in PHP to handle foreign key exceptions on a mysql database? Is there a mysql class that can be used to simplify any code?
Ideally, what I want to do, as an example, is to try to delete a record where it is the foreign key parent to any number of child tables. The foreign key throws the exception, so then I would like to be able to look at each foreign key table and test it, giving meaningful feedback on the tables and number of records causing the exception. This would then be returned as the error so the end user can reference and delete the offending records.
The way I handle this is to set up my database wrapper class to always throw an exception when you encounter a database error. So, for instance, I might have a class called MySQL with the following functions:
public function query($query_string)
{
$this->queryId = mysql_query($query_string,$this->connectionId);
if (! $this->queryId) {
$this->_throwException($query_string);
}
return $this->queryId;
}
private function _throwException($query = null)
{
$msg = mysql_error().". Query was:\n\n".$query.
"\n\nError number: ".mysql_errno();
throw new Exception($msg,mysql_errno());
}
Any time a query fails, a regular PHP exception is thrown. Note that I would throw these from within other places too, like a connect() function or a selectDb() function, depending on whether the operation succeeded or not.
With that set up, you're good to go. Any place you expect that you might need to be handling a database error, do something like the following:
//assume $db has been set up to be an instance of the MySQL class
try {
$db->query("DELETE FROM parent WHERE id=123");
} catch (Exception $e) {
//uh-oh, maybe a foreign key restraint failed?
if ($e->getCode() == 'mysql foreign key error code') {
//yep, it failed. Do some stuff.
}
}
Edit
In response to the poster's comment below, you have some limited information available to you to help diagnose a foreign key issue. The error text created by a failed foreign key restraint and returned by mysql_error() looks something like this:
Cannot delete or update a parent row:
a foreign key constraint fails
(`dbname`.`childtable`, CONSTRAINT `FK_name_1` FOREIGN KEY
(`fieldName`) REFERENCES `parenttable` (`fieldName`));
If your foreign keys are complex enough that you can't be sure what might cause a foreign key error for a given query, then you could probably parse this error text to help figure it out. The command SHOW ENGINE INNODB STATUS returns a more detailed result for the latest foreign key error as well.
Otherwise, you're probably going to have to do some digging yourself. The following query will give you a list of foreign keys on a given table, which you can examine for information:
select * from information_schema.table_constraints
WHERE table_schema=schema() AND table_name='table_name';
Unfortunately, I don't think there's a magic bullet to your solution other than examining the errors and constraints very carefully.
I think the best bet would be for you to do a transaction. That way, the insert will always be valid, or not done at all. That can return an error message that you can work with as well. This will prevent you from having to manually check every table - the db does it for you.

Categories