Subversion-Projekte lars-tiefland.php_share

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

<?php
/*
 *  $Id: PropelPDO.php 1262 2009-10-26 20:54:39Z francois $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information please see
 * <http://propel.phpdb.org>.
 */


/**
 * PDO connection subclass that provides the basic fixes to PDO that are required by Propel.
 *
 * This class was designed to work around the limitation in PDO where attempting to begin
 * a transaction when one has already been begun will trigger a PDOException.  Propel
 * relies on the ability to create nested transactions, even if the underlying layer
 * simply ignores these (because it doesn't support nested transactions).
 *
 * The changes that this class makes to the underlying API include the addition of the
 * getNestedTransactionDepth() and isInTransaction() and the fact that beginTransaction()
 * will no longer throw a PDOException (or trigger an error) if a transaction is already
 * in-progress.
 *
 * @author     Cameron Brunner <cameron.brunner@gmail.com>
 * @author     Hans Lellelid <hans@xmpl.org>
 * @author     Christian Abegg <abegg.ch@gmail.com>
 * @since      2006-09-22
 * @package    propel.util
 */
class PropelPDO extends PDO {

        /**
         * Attribute to use to set whether to cache prepared statements.
         */
        const PROPEL_ATTR_CACHE_PREPARES = -1;

        /**
         * The current transaction depth.
         * @var        int
         */
        protected $nestedTransactionCount = 0;

        /**
         * Cache of prepared statements (PDOStatement) keyed by md5 of SQL.
         *
         * @var        array [md5(sql) => PDOStatement]
         */
        protected $preparedStatements = array();

        /**
         * Whether to cache prepared statements.
         *
         * @var        boolean
         */
        protected $cachePreparedStatements = false;
        
        /**
         * Whether the final commit is possible
         * Is false if a nested transaction is rolled back
         */
        protected $isUncommitable = false;

        /**
         * Gets the current transaction depth.
         * @return     int
         */
        public function getNestedTransactionCount()
        {
                return $this->nestedTransactionCount;
        }

        /**
         * Set the current transaction depth.
         * @param      int $v The new depth.
         */
        protected function setNestedTransactionCount($v)
        {
                $this->nestedTransactionCount = $v;
        }

        /**
         * Decrements the current transaction depth by one.
         */
        protected function decrementNestedTransactionCount()
        {
                $this->nestedTransactionCount--;
        }

        /**
         * Increments the current transaction depth by one.
         */
        protected function incrementNestedTransactionCount()
        {
                $this->nestedTransactionCount++;
        }

        /**
         * Is this PDO connection currently in-transaction?
         * This is equivalent to asking whether the current nested transaction count
         * is greater than 0.
         * @return     boolean
         */
        public function isInTransaction()
        {
                return ($this->getNestedTransactionCount() > 0);
        }
        
        /**
         * Overrides PDO::beginTransaction() to prevent errors due to already-in-progress transaction.
         */
        public function beginTransaction()
        {
                $return = true;
                $opcount = $this->getNestedTransactionCount();
                if ( $opcount === 0 ) {
                        $return = parent::beginTransaction();
                        $this->isUncommitable = false;
                }
                $this->incrementNestedTransactionCount();
                return $return;
        }

        /**
         * Overrides PDO::commit() to only commit the transaction if we are in the outermost
         * transaction nesting level.
         */
        public function commit()
        {
                $return = true;
                $opcount = $this->getNestedTransactionCount();
                if ($opcount > 0) {
                        if ($opcount === 1) {
                                if ($this->isUncommitable) {
                                        throw new PropelException('Cannot commit because a nested transaction was rolled back');
                                } else {
                                        $return = parent::commit();
                                }
                        }
                        $this->decrementNestedTransactionCount();
                }
                return $return;
        }

        /**
         * Overrides PDO::rollBack() to only rollback the transaction if we are in the outermost
         * transaction nesting level
         * @return     boolean Whether operation was successful.
         */
        public function rollBack()
        {
                $return = true;
                $opcount = $this->getNestedTransactionCount();
                if ($opcount > 0) {
                        if ($opcount === 1) { 
                                $return = parent::rollBack(); 
                        } else {
                                $this->isUncommitable = true;
                        }
                        $this->decrementNestedTransactionCount(); 
                }
                return $return;
        }

        /**
        * Rollback the whole transaction, even if this is a nested rollback
        * and reset the nested transaction count to 0.
        * @return     boolean Whether operation was successful.
        */
        public function forceRollBack()
        {
                $return = true;
                $opcount = $this->getNestedTransactionCount();
                if ($opcount > 0) {
                        // If we're in a transaction, always roll it back
                        // regardless of nesting level.
                        $return = parent::rollBack();
                        
                        // reset nested transaction count to 0 so that we don't
                        // try to commit (or rollback) the transaction outside this scope.
                        $this->nestedTransactionCount = 0;
                }
                return $return;
        }
  
        /**
         * Sets a connection attribute.
         *
         * This is overridden here to provide support for setting Propel-specific attributes
         * too.
         *
         * @param      int $attribute The attribute to set (e.g. PropelPDO::PROPEL_ATTR_CACHE_PREPARES).
         * @param      mixed $value The attribute value.
         */
        public function setAttribute($attribute, $value)
        {
                switch($attribute) {
                        case self::PROPEL_ATTR_CACHE_PREPARES:
                                $this->cachePreparedStatements = $value;
                                break;
                        default:
                                parent::setAttribute($attribute, $value);
                }
        }

        /**
         * Gets a connection attribute.
         *
         * This is overridden here to provide support for setting Propel-specific attributes
         * too.
         *
         * @param      int $attribute The attribute to get (e.g. PropelPDO::PROPEL_ATTR_CACHE_PREPARES).
         */
        public function getAttribute($attribute)
        {
                switch($attribute) {
                        case self::PROPEL_ATTR_CACHE_PREPARES:
                                return $this->cachePreparedStatements;
                                break;
                        default:
                                return parent::getAttribute($attribute);
                }
        }

        /**
         * Overrides PDO::prepare() to add query caching support if the
         * PropelPDO::PROPEL_ATTR_CACHE_PREPARES was set to true.
         * .
         * @param      string $sql
         * @param      array
         * @return     PDOStatement
         */
        public function prepare($sql, $driver_options = array())
        {
                if ($this->cachePreparedStatements) {
                        $key = $sql;
                        if (!isset($this->preparedStatements[$key])) {
                                $stmt = parent::prepare($sql, $driver_options);
                                $this->preparedStatements[$key] = $stmt;
                                return $stmt;
                        } else {
                                return $this->preparedStatements[$key];
                        }
                } else {
                        return parent::prepare($sql, $driver_options);
                }
        }

        /**
         * Clears any stored prepared statements for this connection.
         */
        public function clearStatementCache()
        {
                $this->preparedStatements = array();
        }

}