Yii Framework Forum: Extending Cdbtransaction - Yii Framework Forum

Jump to content

Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

Extending Cdbtransaction Rate Topic: ***** 1 Votes

#1 User is offline   Keith 

  • Elite Member
  • Yii
  • Group: Moderators
  • Posts: 1,697
  • Joined: 04-March 10
  • Location:UK

Posted 12 October 2012 - 10:15 AM

I currently use a behavior to implement history logging, which uses afterFind() and beforeSave() to determine any changes and log them to a history table (MySQL archive engine).

The archive table format doesn't support transactions, so my existing solution could show history for an update that never occurred, as a result of the actual changes being rolled back.

I've thought of a way to solve this:
  • Override CDbTransaction
  • Add a private array to hold the outstanding history
  • Add a method to append to the history array (called from the behavior)
  • Override CDbTransaction::commit() to save all history after calling the parent implementation
  • Update the behavior to log history to the active transaction if there is one, or output straight to the history table if not


This would enable the history to be logged only if the transaction completes successfully.
Unfortunately, the CDbTransaction class is hardcoded in CDbConnection::beginTransaction().

I'm intending to extend CDbConnection and override the beginTransaction() method, so that I can instantiate my new transaction class.

Is this the best way to approach this problem, or is there a more sensible option?
0

#2 User is offline   Rodrigo Coelho 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 665
  • Joined: 05-August 10
  • Location:Rio de Janeiro, Brazil

Posted 12 October 2012 - 05:51 PM

Spots of this kind are the few places where Yii isn't much extensible.
I'm interested in the solution, too.
0

#3 User is offline   Keith 

  • Elite Member
  • Yii
  • Group: Moderators
  • Posts: 1,697
  • Joined: 04-March 10
  • Location:UK

Posted 15 October 2012 - 05:00 AM

I'm about to go ahead and implement this. Any last minute suggestions anyone?
0

#4 User is offline   Keith 

  • Elite Member
  • Yii
  • Group: Moderators
  • Posts: 1,697
  • Joined: 04-March 10
  • Location:UK

Posted 16 October 2012 - 05:11 AM

*
POPULAR

Okay, I don't like how I've had to override a private instance variable, but this implementation seems to work.

class DbConnection extends CDbConnection
{
	private $_transaction; // Override parent
	
	public function beginTransaction() 
	{ 
		Yii::trace('Starting transaction', 'DbConnection'); 
		$this->setActive(true); 
		$this->getPdoInstance()->beginTransaction(); 
		return $this->_transaction=new DbTransaction($this); 
	}
}

class DbTransaction extends CDbTransaction {
	
	private $outstandingHistory = array();
	
	public function appendHistory($history)
	{
		$this->outstandingHistory[] = $history;
	}
	
	public function commit()
	{
		parent::commit();
		
		foreach ($this->outstandingHistory as $history)
			$history->save();
		
		$this->outstandingHistory = array();
	}
}


Each element of outstandingHistory is a History model instance. If there's an active transaction, the history logging behavior appends the instance to this array. If there's no active transaction, the behavior simply saves the History model instance.

I figured I'd post it in case anyone else needs similar functionality in the future.
5

#5 User is offline   tfotherby 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 12
  • Joined: 11-January 11
  • Location:Reading, UK

Posted 01 November 2013 - 11:59 AM

Thanks for starting this discussion Keith. I found myself in the same situation as you. However I think if you are redeclaring $_transaction in your extended connection class you also need to port the getCurrentTransaction() method to your extended class as well otherwise any calls to it will return the parents $_transaction.

I wanted to put a SQL comment in our transaction commits so I ended up with this monstrosity:

// Extended so we can return our own Transaction class
class DbConnection extends CDbConnection
{
	private $_transaction; // CODE SMELL!! Override parent because unfortunately it's private in the parent.


	// carbon-copy of the parent method
	public function getCurrentTransaction() 
	{
		if($this->_transaction!==null)
			if($this->_transaction->getActive())
				return $this->_transaction;
		return null;
	}

	public function beginTransaction() 
	{ 
		Yii::trace('Starting transaction', 'DbConnection'); 
		$this->setActive(true); 
		$this->getPdoInstance()->beginTransaction(); 
		return $this->_transaction=new DbTransaction($this); // Note: we return our extended Transaction class
	}
}

class DbTransaction extends CDbTransaction
{	
	public function commit()
	{
		if ($this->getActive() && $this->getConnection()->getActive()) {

			// Get a random string to use as a ID for this commit
			$comment = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 15);

			$e = new Exception;
			Yii::trace("COMMIT with comment {$comment} - Stack trace:\n".print_r($e->getTraceAsString(),true),'app.components.dbtransaction');

			// Instead of using PDO::commit, use raw COMMIT with a SQL comment so we can marry the slow-log with the server logs.
			$this->getConnection()->createCommand("COMMIT /* {$comment} */")->execute();

			$this->setActive(false);
		} else
			throw new CDbException(Yii::t('yii','CDbTransaction is inactive and cannot perform commit or roll back operations.'));
	}
}


Note: this is not production code, it is only temporary to try to debug some commits that are in our mysql slow-log but we aren't sure which transaction they relate too.
0

#6 User is offline   nineinchnick 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 622
  • Joined: 12-September 11
  • Location:Bialystok, Poland

Posted 01 November 2013 - 03:05 PM

If you need to support transactions, why not implement history using triggers on tables?
Don't be a dick.
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users