Location: PHPKode > projects > Recess PHP Framework > recess/recess/database/pdo/PdoDataSource.class.php
<?php
Library::import('recess.database.pdo.exceptions.DataSourceCouldNotConnectException');
Library::import('recess.database.pdo.exceptions.ProviderDoesNotExistException');
Library::import('recess.database.pdo.PdoDataSet');

Library::import('recess.database.pdo.RecessTableDescriptor');
Library::import('recess.database.pdo.RecessColumnDescriptor');

/**
 * A PDO wrapper in the Recess PHP Framework that provides a single interface for commonly 
 * needed operations (i.e.: list tables, list columns in a table, etc).
 * 
 * @author Kris Jordan <hide@address.com>
 * @copyright 2008, 2009 Kris Jordan
 * @package Recess PHP Framework
 * @license MIT
 * @link http://www.recessframework.org/
 */
class PdoDataSource extends PDO {
	const PROVIDER_CLASS_LOCATION = 'recess.database.pdo.';
	const PROVIDER_CLASS_SUFFIX = 'DataSourceProvider';
	const CACHE_PREFIX = 'Recess::PdoDS::';
	
	protected $provider = null;
	
	protected $cachePrefix;
	
	/**
	 * Creates a data source instance to represent a connection to the database.
	 * The first argument can either be a string DSN or an array which contains
	 * the construction arguments.
	 *
	 * @param mixed $dsn String DSN or array of arguments (dsn, username, password)
	 * @param string $username
	 * @param string $password
	 * @param array $driver_options
	 */
	function __construct($dsn, $username = '', $password = '', $driver_options = array()) {
		if(is_array($dsn)) {
			$args = $dsn;
			if(isset($args[0])) { $dsn = $args[0]; }
			if(isset($args[1])) { $username = $args[1];	}
			if(isset($args[2])) { $password = $args[2];	}
			if(isset($args[3])) { $driver_options = $args[3]; }
		}
		
		try {
			parent::__construct($dsn, $username, $password, $driver_options);
			parent::setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		} catch (PDOException $exception) {
			throw new DataSourceCouldNotConnectException($exception->getMessage(), get_defined_vars());
		}
		
		$this->cachePrefix = self::CACHE_PREFIX . $dsn . '::*::';
		
		$this->provider = $this->instantiateProvider();
	}
	
	/**
	 * Locate the pdo driver specific data source provider, instantiate, and return.
	 * Throws ProviderDoesNotExistException for a pdo driver without a Recess provider.
	 *
	 * @return IPdoDataSourceProvider
	 */
	protected function instantiateProvider() {
		$driver = ucfirst(parent::getAttribute(PDO::ATTR_DRIVER_NAME));
		$providerClass = $driver . self::PROVIDER_CLASS_SUFFIX;
		$providerFullyQualified = self::PROVIDER_CLASS_LOCATION . $providerClass;
		// Library::import($providerFullyQualified);
		if(Library::classExists($providerFullyQualified)) {
			$provider = new $providerClass;
			$provider->init($this);
			return $provider;
		} else {
			throw new ProviderDoesNotExistException($providerClass, get_defined_vars());	
		}
	}
	
	/**
	 * Begin a select operation by returning a new, unrealized PdoDataSet
	 *
	 * @param string $table Optional parameter that sets the from clause of the select to a table.
	 * @return PdoDataSet
	 */
	function select($table = '') {
		if($table != '') {
			$PdoDataSet = new PdoDataSet($this);
			return $PdoDataSet->from($table);
		} else {
			return new PdoDataSet($this);
		}
	}
	
	/**
	 * Takes the SQL and arguments (array of Criterion) and returns an array
	 * of objects of type $className.
	 * 
	 * @todo Determine edge conditions and throws.
	 * @param string $query
	 * @param array(Criterion) $arguments 
	 * @param string $className the type to fill from query results.
	 * @return array($className)
	 */
	function queryForClass(SqlBuilder $builder, $className) {
		$statement = $this->provider->getStatementForBuilder($builder,'select',$this);
		$statement->setFetchMode(PDO::FETCH_CLASS, $className, array());
		$statement->execute();
		return $this->provider->fetchAll($statement);
	}
	
	/**
	 * Execute the query from a SqlBuilder instance.
	 *
	 * @param SqlBuilder $builder
	 * @param string $action
	 * @return boolean
	 */
	function executeSqlBuilder(SqlBuilder $builder, $action) {
		return $this->provider->executeSqlBuilder($builder, $action, $this);
	}
	
	function executeStatement($statement, $arguments) {
		$statement = $this->prepareStatement($statement, $arguments);
		return $statement->execute();
	}
	
	function explainStatement($statement, $arguments) {
		$statement = $this->prepareStatement('EXPLAIN QUERY PLAN ' . $statement, $arguments);
		$statement->execute();
		return $statement->fetchAll();
	}
	
	function prepareStatement($statement, $arguments) {
		try {
			$statement = $this->prepare($statement);
		} catch(PDOException $e) {
			throw new RecessException($e->getMessage() . ' SQL: ' . $statement,get_defined_vars());
		}
		foreach($arguments as &$argument) {
			// Begin workaround for PDO's poor numeric binding
			$queryParameter = $argument->getQueryParameter();
			if(is_numeric($queryParameter)) { continue; } 
			// End Workaround
			$statement->bindValue($argument->getQueryParameter(), $argument->value);
		}
		return $statement;
	}
	
	/**
	 * List the tables in a data source alphabetically.
	 * @return array(string) The tables in the data source
	 */
	function getTables() {
		$cacheKey = $this->cachePrefix . 'Tables';
		$tables = Cache::get($cacheKey);
		if(!$tables) {
			$tables = $this->provider->getTables();
			Cache::set($this->cachePrefix . 'Tables', $tables);
		}
		return $tables;
	}
	
	/**
	 * List the column names of a table alphabetically.
	 * @param string $table Table whose columns to list.
	 * @return array(string) Column names sorted alphabetically.
	 */
	function getColumns($table) {
		$cacheKey = $this->cachePrefix . $table . '::Columns';
		$columns = Cache::get($cacheKey);
		if(!$columns) {
			$columns = $this->provider->getColumns($table);
			Cache::set($cacheKey, $columns);
		}
		return $columns;
	}
	
	/**
	 * Retrieve the a table's RecessTableDescriptor.
	 *
	 * @param string $table
	 * @return RecessTableDescriptor
	 */
	function getTableDescriptor($table) {
		$cacheKey = $this->cachePrefix . $table . '::Descriptor';
		$descriptor = Cache::get($cacheKey);
		if(!$descriptor) {
			$descriptor = $this->provider->getTableDescriptor($table);
			Cache::set($cacheKey, $descriptor);
		}
		return $descriptor;
	}
	
	/**
	 * Take a table descriptor and apply it / verify it on top of the
	 * table descriptor returned from a database. This is used to ensure
	 * a model's marked up fields are in congruence with the table. Also
	 * checks to ensure the number of columns in the cascaded descriptor
	 * do not outnumber the actual number of columns. Finally with a database
	 * like sqlite which largely ignores column typing it enables the model
	 * to inform the actual Recess type of the column.
	 *
	 * @param string $table
	 * @param RecessTableDescriptor $descriptor
	 */
	function cascadeTableDescriptor($table, $descriptor) { 
		$cacheKey = $this->cachePrefix . $table . '::Descriptor';
		Cache::set($cacheKey, $this->provider->cascadeTableDescriptor($table, $descriptor));
	}
	
	/**
	 * Drop a table from the database.
	 *
	 * @param string $table
	 */
	function dropTable($table) {
		return $this->provider->dropTable($table);
	}
	
	/**
	 * Empty a table in the database.
	 *
	 * @param string $table
	 */
	function emptyTable($table) {
		return $this->provider->emptyTable($table);
	}
	
	function createTableSql($tableDescriptor) {
		return $this->provider->createTableSql($tableDescriptor);
	}
}

?>
Return current item: Recess PHP Framework