Location: PHPKode > projects > Maintainable PHP Framework > vendor/Horde/Db/Adapter/Abstract.php
<?php
/**
 * Copyright 2007 Maintainable Software, LLC
 * Copyright 2008 The Horde Project (http://www.horde.org/)
 *
 * @author     Mike Naberezny <hide@address.com>
 * @author     Derek DeVries <hide@address.com>
 * @author     Chuck Hagenbuch <hide@address.com>
 * @license    http://opensource.org/licenses/bsd-license.php
 * @category   Horde
 * @package    Horde_Db
 * @subpackage Adapter
 */

/**
 * @author     Mike Naberezny <hide@address.com>
 * @author     Derek DeVries <hide@address.com>
 * @author     Chuck Hagenbuch <hide@address.com>
 * @license    http://opensource.org/licenses/bsd-license.php
 * @category   Horde
 * @package    Horde_Db
 * @subpackage Adapter
 */
abstract class Horde_Db_Adapter_Abstract
{
    /**
     * Config options
     * @var array
     */
    protected $_config = array();

    /**
     * @var mixed
     */
    protected $_connection = null;

    /**
     * @var boolean
     */
    protected $_transactionStarted = false;

    /**
     * @var int
     */
    protected $_rowCount = null;

    /**
     * @var int
     */
    protected $_runtime = null;

    /**
     * @var boolean
     */
    protected $_active = null;

    /**
     * @var Cache object
     */
    protected $_cache = null;

    /**
     * @var Logger
     */
    protected $_logger = null;

    /**
     * @var Horde_Db_Adapter_Abstract_Schema
     */
    protected $_schema = null;

    /**
     * @var string
     */
    protected $_schemaClass = null;

    /**
     * @var array
     */
    protected $_schemaMethods = array();


    /*##########################################################################
    # Construct/Destruct
    ##########################################################################*/

    /**
     * @param array $config Configuration options and optional objects (logger,
     * cache, etc.)
     */
    public function __construct($config)
    {
        // Create a stub if we don't have a useable cache.
        if (isset($config['cache'])
            && is_callable(array($config['cache'], 'get'))
            && is_callable(array($config['cache'], 'set'))) {
            $this->_cache = $config['cache'];
            unset($config['cache']);
        } else {
            $this->_cache = new Horde_Support_Stub;
        }

        // Create a stub if we don't have a useable logger.
        if (isset($config['logger'])
            && is_callable(array($config['logger'], 'log'))) {
            $this->_logger = $config['logger'];
            unset($config['logger']);
        } else {
            $this->_logger = new Horde_Support_Stub;
        }

        $this->_config  = $config;
        $this->_runtime = 0;

        // Create the database-specific (but not adapter specific) schema
        // object.
        if (!$this->_schemaClass)
            $this->_schemaClass = get_class($this).'_Schema';
        $this->_schema = new $this->_schemaClass($this, array(
            'cache' => $this->_cache,
            'logger' => $this->_logger));
        $this->_schemaMethods = array_flip(get_class_methods($this->_schema));

        $this->connect();
    }

    /**
     * Free any resources that are open.
     */
    public function __destruct()
    {
        $this->disconnect();
    }


    /*##########################################################################
    # Object composition
    ##########################################################################*/

    /**
     * Delegate calls to the schema object.
     *
     * @param  string  $method
     * @param  array   $args
     */
    public function __call($method, $args)
    {
        if (isset($this->_schemaMethods[$method])) {
            return call_user_func_array(array($this->_schema, $method), $args);
        }

        throw new BadMethodCallException('Call to undeclared method "'.$method.'"');
    }


    /*##########################################################################
    # Public
    ##########################################################################*/

    /**
     * Returns the human-readable name of the adapter.  Use mixed case - one
     * can always use downcase if needed.
     *
     * @return  string
     */
    public function adapterName()
    {
        return 'Abstract';
    }

    /**
     * Does this adapter support migrations?  Backend specific, as the
     * abstract adapter always returns +false+.
     *
     * @return  boolean
     */
    public function supportsMigrations()
    {
        return false;
    }

    /**
     * Does this adapter support using DISTINCT within COUNT?  This is +true+
     * for all adapters except sqlite.
     *
     * @return  boolean
     */
    public function supportsCountDistinct()
    {
        return true;
    }

    /**
     * Should primary key values be selected from their corresponding
     * sequence before the insert statement?  If true, next_sequence_value
     * is called before each insert to set the record's primary key.
     * This is false for all adapters but Firebird.
     */
    public function prefetchPrimaryKey($tableName = null)
    {
        return false;
    }

    /**
     * Reset the timer
     *
     * @return  int
     */
    public function resetRuntime()
    {
        $runtime = $this->_runtime;
        $this->_runtime = 0;
        return $this->_runtime;
    }


    /*##########################################################################
    # Connection Management
    ##########################################################################*/

    /**
     * Connect to the db
     */
    abstract public function connect();

    /**
     * Is the connection active
     *
     * @return  boolean
     */
    public function isActive()
    {
        return $this->_active;
    }

    /**
     * Reconnect to the db
     */
    public function reconnect()
    {
        $this->disconnect();
        $this->connect();
    }

    /**
     * Disconnect from db
     */
    public function disconnect()
    {
        $this->_connection = null;
        $this->_active = false;
    }

    /**
     * Provides access to the underlying database connection. Useful for when
     * you need to call a proprietary method such as postgresql's lo_* methods
     *
     * @return  resource
     */
    public function rawConnection()
    {
        return $this->_connection;
    }


    /*##########################################################################
    # Database Statements
    ##########################################################################*/

    /**
     * Returns an array of records with the column names as keys, and
     * column values as values.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     * @return  Traversable
     */
    public function select($sql, $arg1=null, $arg2=null)
    {
        return $this->execute($sql, $arg1, $arg2);
    }

    /**
     * Returns an array of record hashes with the column names as keys and
     * column values as values.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     */
    public function selectAll($sql, $arg1=null, $arg2=null)
    {
        $rows = array();
        $result = $this->select($sql, $arg1, $arg2);
        if ($result) {
            foreach ($result as $row) {
                $rows[] = $row;
            }
        }
        return $rows;
    }

    /**
     * Returns a record hash with the column names as keys and column values
     * as values.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     * @return  array
     */
    public function selectOne($sql, $arg1=null, $arg2=null)
    {
        $result = $this->selectAll($sql, $arg1, $arg2);
        return $result ? current($result) : array();
    }

    /**
     * Returns a single value from a record
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     * @return  string
     */
    public function selectValue($sql, $arg1=null, $arg2=null)
    {
        $result = $this->selectOne($sql, $arg1, $arg2);
        return $result ? current($result) : null;
    }

    /**
     * Returns an array of the values of the first column in a select:
     *   select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     */
    public function selectValues($sql, $arg1=null, $arg2=null)
    {
        $result = $this->selectAll($sql, $arg1, $arg2);
        foreach ($result as $row) {
            $values[] = current($row);
        }
        return isset($values) ? $values : array();
    }

    /**
     * Executes the SQL statement in the context of this connection.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     */
    public function execute($sql, $arg1=null, $arg2=null)
    {
        if (is_array($arg1)) {
            $sql = $this->_replaceParameters($sql, $arg1);
            $name = $arg2;
        } else {
            $name = $arg1;
        }

        $t = new Horde_Support_Timer;
        $t->push();

        try {
            $stmt = $this->_connection->query($sql);
        } catch (Exception $e) {
            $this->_logInfo($sql, 'QUERY FAILED: ' . $e->getMessage());
            $this->_logInfo($sql, $name);
            throw new Horde_Db_Exception((string)$e->getMessage(), (int)$e->getCode());
        }

        $this->_logInfo($sql, $name, $t->pop());

        $this->_rowCount = $stmt ? $stmt->rowCount() : 0;
        return $stmt;
    }

    /**
     * Returns the last auto-generated ID from the affected table.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     * @param   string  $pk
     * @param   int     $idValue
     * @param   string  $sequenceName
     */
    public function insert($sql, $arg1=null, $arg2=null, $pk=null, $idValue=null, $sequenceName=null)
    {
        $this->execute($sql, $arg1, $arg2);
        return isset($idValue) ? $idValue : $this->_connection->lastInsertId();
    }

    /**
     * Executes the update statement and returns the number of rows affected.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     */
    public function update($sql, $arg1=null, $arg2=null)
    {
        $this->execute($sql, $arg1, $arg2);
        return $this->_rowCount;
    }

    /**
     * Executes the delete statement and returns the number of rows affected.
     *
     * @param   string  $sql
     * @param   mixed   $arg1  Either an array of bound parameters or a query name.
     * @param   string  $arg2  If $arg1 contains bound parameters, the query name.
     */
    public function delete($sql, $arg1=null, $arg2=null)
    {
        $this->execute($sql, $arg1, $arg2);
        return $this->_rowCount;
    }

    /**
     * Check if a transaction has been started
     */
    public function transactionStarted()
    {
        return $this->_transactionStarted;
    }

    /**
     * Begins the transaction (and turns off auto-committing).
     */
    public function beginDbTransaction()
    {
        $this->_transactionStarted = true;
        $this->_connection->beginTransaction();
    }

    /**
     * Commits the transaction (and turns on auto-committing).
     */
    public function commitDbTransaction()
    {
        $this->_connection->commit();
        $this->_transactionStarted = false;
    }

    /**
     * Rolls back the transaction (and turns on auto-committing). Must be
     * done if the transaction block raises an exception or returns false.
     */
    public function rollbackDbTransaction()
    {
        if (! $this->_transactionStarted) { return; }

        $this->_connection->rollBack();
        $this->_transactionStarted = false;
    }

    /**
     * Appends +LIMIT+ and +OFFSET+ options to a SQL statement.
     *
     * @param   string  $sql
     * @param   array   $options
     * @return  string
     */
    public function addLimitOffset($sql, $options)
    {
        if (isset($options['limit']) && $limit = $options['limit']) {
            if (isset($options['offset']) && $offset = $options['offset']) {
                $sql .= " LIMIT $offset, $limit";
            } else {
                $sql .= " LIMIT $limit";
            }
        }
        return $sql;
    }

    public function sanitizeLimit($limit)
    {
        if (strpos($limit, ',') !== false) {
            return implode(',', array_map(create_function('$i', 'return (int)$i;'), explode(',', $limit)));
        } else return (int)$limit;
    }

    /**
     * Appends a locking clause to an SQL statement.
     * This method *modifies* the +sql+ parameter.
     *   # SELECT * FROM suppliers FOR UPDATE
     *   add_lock! 'SELECT * FROM suppliers', :lock => true
     *   add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
     */
    public function addLock(&$sql, $options = array())
    {
        if (isset($options['lock']) && is_string($options['lock'])) {
            $sql .= ' ' . $lock;
        } else {
            $sql .= ' FOR UPDATE';
        }
    }

    /**
     * Inserts the given fixture into the table. Overridden in adapters that
     * require something beyond a simple insert (eg. Oracle).
     */
    public function insertFixture($fixture, $tableName)
    {
        /*@TODO*/
        return $this->execute("INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert');
    }

    public function emptyInsertStatement($tableName)
    {
        return 'INSERT INTO '.$this->quoteTableName($tableName).' VALUES(DEFAULT)';
    }


    /*##########################################################################
    # Protected
    ##########################################################################*/

    /**
     * Replace ? in a SQL statement with quoted values from $args
     *
     * @param   string  $sql
     * @param   array   $args
     */
    protected function _replaceParameters($sql, $args)
    {
        $paramCount = substr_count($sql, '?');
        if (count($args) != $paramCount) {
            throw new Horde_Db_Exception('Parameter count mismatch');
        }

        $sqlPieces = explode('?', $sql);
        $sql = array_shift($sqlPieces);
        while (count($sqlPieces)) {
            $sql .= $this->quote(array_shift($args)) . array_shift($sqlPieces);
        }
        return $sql;
    }

    /**
     * Logs the SQL query for debugging.
     *
     * @param   string  $sql
     * @param   string  $name
     * @param   float   $runtime
     */
    protected function _logInfo($sql, $name, $runtime=null)
    {
        $name = (empty($name) ? '' : $name)
              . (empty($runtime) ? '' : sprintf(" (%.4fs)", $runtime));
        $this->_logger->info($this->_formatLogEntry($name, $sql));
    }

    /**
     * Formats the log entry.
     *
     * @param   string  $message
     * @param   string  $sql
     */
    protected function _formatLogEntry($message, $sql)
    {
        $sql = preg_replace("/\s+/", ' ', $sql);
        $sql = "\n\t".wordwrap($sql, 70, "\n\t  ", 1);
        return "SQL $message  $sql";
    }

}
Return current item: Maintainable PHP Framework