exception ‘Zend_Db_Table_Row_Exception’ with message ‘Cannot refresh row as parent is missing’

I ran through the following exception while using some Zend_Db_Table_Row instances:

exception 'Zend_Db_Table_Row_Exception' with message 'Cannot refresh row as parent is missing'
in /var/www/library/ZendFramework-1.7.1-minimal/library/Zend/Db/Table/Row/Abstract.php:755

The reason for this exception, which was not easy to find out, is simply that I am inserting data using sql transactions. Row instances automatically refresh themselves upon insertion / deletion making a db call.

Problem is that if your transaction is not commited, then this refresh call will fail and raise the above exception.

solution

The solution I implemented to have rows refreshed automatically once a successful transaction commit occured is as follow:

  1. I subclassed Zend_Db_Adapter so that I can know if a transaction is already running I can overwrite commit method for it to refresh rows that could not be refreshed. Here is the code:
    /**
     * @package core
     * @subpackage global
     */
    
    require_once 'Zend/Db/Adapter/Pdo/Mysql.php';
    
    /**
     * Extends Zend db adapter to add extra functionalities to handle sql transactions
     */
    class Library_Db_Adapter_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql
    {
    	/**
    	 * Array of instances waiting to be refreshed once sql transaction is commited
     	 * Mutli-dimensional array: 1 entry per nested running transaction
    	 * @var array [Zend_Db_Table_Row,]
    	 */
    	protected $_rows_waiting_refresh = array();
    
    	/**
    	 * Keep track of the number of currently running transactions
    	 * @var int
    	 */
    	protected $_transaction_count = 0;
    
    	/**
    	 * Add a row waiting for refresh once current transaction gets commited
    	 *
    	 * @param Zend_Db_Table_Row_Abstract $row
    	 */
    	public function addRowWaitingRefresh(Zend_Db_Table_Row_Abstract $row)
    	{
    		if (!$this->isTransactionRunning()) {
    			throw new Library_FenvException_DevError('cannot add row to wait refresh once transaction is commited, no transaction currently running');
    		}
    		$this->_rows_waiting_refresh[$this->_transaction_count - 1][] = $row;
    	}
    
    	/**
    	 * Return true if a transaction is running, false otherwise
    	 *
    	 * @return bool
    	 */
    	public function isTransactionRunning()
    	{
    		return !!$this->_transaction_count;
    	}
    
    	/**
    	 * Start a transaction
    	 *
    	 * @return parent::beginTransaction
    	 */
    	public function beginTransaction()
    	{
    		if ($output = parent::beginTransaction()) {
    			$this->_rows_waiting_refresh[$this->_transaction_count] = array();
    			++$this->_transaction_count;
    		}
    		return $ output;
    	}
    
    	/**
    	 * Rollback a transaction
    	 *
    	 * @return parent::rollBack
    	 */
    	public function rollBack()
    	{
    		if ($output = parent::rollBack()) {
    			--$this->_transaction_count;
    
    			// do not refresh waiting instances and remove them from waiting list
    			unset($this->_rows_waiting_refresh[$this->_transaction_count]);		}
    		return $output;
    	}
    
    	/**
    	 * Commit a transaction
    	 *
    	 * @return parent::commit
    	 */
    	public function commit()
    	{
    		if ($output = parent::commit()) {
    			--$this->_transaction_count;
    
    			//update instances waiting to be refreshed
    			while ($row = array_shift($this->_rows_waiting_refresh[$this->_transaction_count])) {
    				$row->refresh();
    			}
    		}
    		return $output;
    	}
    }
  2. Then we need to subclass Zend_Db_Table_Row_Abstract class to overwrite ::_refresh() as follow:
    class Library_Db_Row extends Zend_Db_Table_Row_Abstract
    {
    
    	/**
    	 * Upon insert/update, self::_refresh() is automatically called, generating an error if refresh returns no data.
    	 *
    	 * Problem is that if you're using sql transaction, refresh will fail and generate an exception
    	 * Therefore we prevent automatic refresh if a transaction is running, and store instance in an array to be refreshed as soon as transaction is commited.
    	 *
    	 */
    	protected function _refresh()
    	{
    		$dba = $this->getTable()->getAdapter();
    		if ($dba->isTransactionRunning()) {
    			$dba->addRowWaitingRefresh($this);
    		} else {
    			return parent::_refresh();
    		}
    	}
    }

That’s it! You can know use Zend Framework with db transactions.

sources

2 Comments: Trackback URL | Comments RSS

  1. William Says:

    Something doesnt make sense here. When you’re in a transaction, you’re in a “session” where all inserts and updates and deletes will be visible to you until you rollback and commit. Other DB connections won’t see your changes, but you will.

    I’m not sure what dabatase you’re using, but you certainly *should* not have this _refresh error as Zend_Db_Table_Row will be able to find the newly inserted row and refresh it.

  2. remy Says:

    William,

    Thx for your explanation, I investigated a bit further and you’re 100% right.
    My newly inserted row was not returned, but it was not due to running an sql transaction, but to a filter I put when retrieving row that kept newly inserted nodes out of returned rowset.

    Above all, if a transaction is indeed a “session”, it made no sense at all
    (by the way my db was mysql with InnoDb storage engine)

    My bad, thx a lot
    Remy

Post a Comment

Your email is never published nor shared. You're allow to say what you want...

Powered by sweet Captcha