Commit 00215994 by Qiang Xue

w

parent 5e1e3fd1
<?php
/**
* CDbException class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbException represents an exception that is caused by some DB-related operations.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbException.php 2799 2011-01-01 19:31:13Z qiang.xue $
* @package system.db
* @since 1.0
*/
class CDbException extends CException
{
/**
* @var mixed the error info provided by a PDO exception. This is the same as returned
* by {@link http://www.php.net/manual/en/pdo.errorinfo.php PDO::errorInfo}.
* @since 1.1.4
*/
public $errorInfo;
/**
* Constructor.
* @param string $message PDO error message
* @param integer $code PDO error code
* @param mixed $errorInfo PDO error info
*/
public function __construct($message, $code = 0, $errorInfo = null)
{
$this->errorInfo = $errorInfo;
parent::__construct($message, $code);
}
}
\ No newline at end of file
<?php
/**
* CMysqlSchema class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbMigration is the base class for representing a database migration.
*
* CDbMigration is designed to be used together with the "yiic migrate" command.
*
* Each child class of CDbMigration represents an individual database migration which
* is identified by the child class name.
*
* Within each migration, the {@link up} method contains the logic for "upgrading"
* the database used in an application; while the {@link down} method contains "downgrading"
* logic. The "yiic migrate" command manages all available migrations in an application.
*
* CDbMigration provides a set of convenient methods for manipulating database data and schema.
* For example, the {@link insert} method can be used to easily insert a row of data into
* a database table; the {@link createTable} method can be used to create a database table.
* Compared with the same methods in {@link CDbCommand}, these methods will display extra
* information showing the method parameters and execution time, which may be useful when
* applying migrations.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbMigration.php 3218 2011-05-13 00:06:44Z alexander.makarow $
* @package system.db
* @since 1.1.6
*/
abstract class CDbMigration extends CComponent
{
private $_db;
/**
* This method contains the logic to be executed when applying this migration.
* Child classes may implement this method to provide actual migration logic.
* @return boolean
*/
public function up()
{
$transaction = $this->getDbConnection()->beginTransaction();
try
{
if ($this->safeUp() === false)
{
$transaction->rollBack();
return false;
}
$transaction->commit();
}
catch(Exception $e)
{
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$transaction->rollBack();
return false;
}
}
/**
* This method contains the logic to be executed when removing this migration.
* The default implementation throws an exception indicating the migration cannot be removed.
* Child classes may override this method if the corresponding migrations can be removed.
* @return boolean
*/
public function down()
{
$transaction = $this->getDbConnection()->beginTransaction();
try
{
if ($this->safeDown() === false)
{
$transaction->rollBack();
return false;
}
$transaction->commit();
}
catch(Exception $e)
{
echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$transaction->rollBack();
return false;
}
}
/**
* This method contains the logic to be executed when applying this migration.
* This method differs from {@link up} in that the DB logic implemented here will
* be enclosed within a DB transaction.
* Child classes may implement this method instead of {@link up} if the DB logic
* needs to be within a transaction.
* @return boolean
* @since 1.1.7
*/
public function safeUp()
{
}
/**
* This method contains the logic to be executed when removing this migration.
* This method differs from {@link down} in that the DB logic implemented here will
* be enclosed within a DB transaction.
* Child classes may implement this method instead of {@link up} if the DB logic
* needs to be within a transaction.
* @return boolean
* @since 1.1.7
*/
public function safeDown()
{
}
/**
* Returns the currently active database connection.
* By default, the 'db' application component will be returned and activated.
* You can call {@link setDbConnection} to switch to a different database connection.
* Methods such as {@link insert}, {@link createTable} will use this database connection
* to perform DB queries.
* @return CDbConnection the currently active database connection
*/
public function getDbConnection()
{
if ($this->_db === null)
{
$this->_db = Yii::app()->getComponent('db');
if (!$this->_db instanceof CDbConnection)
throw new CException(Yii::t('yii', 'The "db" application component must be configured to be a CDbConnection object.'));
}
return $this->_db;
}
/**
* Sets the currently active database connection.
* The database connection will be used by the methods such as {@link insert}, {@link createTable}.
* @param CDbConnection $db the database connection component
*/
public function setDbConnection($db)
{
$this->_db = $db;
}
/**
* Executes a SQL statement.
* This method executes the specified SQL statement using {@link dbConnection}.
* @param string $sql the SQL statement to be executed
* @param array $params input parameters (name=>value) for the SQL execution. See {@link CDbCommand::execute} for more details.
* @since 1.1.7
*/
public function execute($sql, $params = array())
{
echo " > execute SQL: $sql ...";
$time = microtime(true);
$this->getDbConnection()->createCommand($sql)->execute($params);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Creates and executes an INSERT SQL statement.
* The method will properly escape the column names, and bind the values to be inserted.
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column data (name=>value) to be inserted into the table.
*/
public function insert($table, $columns)
{
echo " > insert into $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->insert($table, $columns);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Creates and executes an UPDATE SQL statement.
* The method will properly escape the column names and bind the values to be updated.
* @param string $table the table to be updated.
* @param array $columns the column data (name=>value) to be updated.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please
* refer to {@link CDbCommand::where} on how to specify conditions.
* @param array $params the parameters to be bound to the query.
*/
public function update($table, $columns, $conditions = '', $params = array())
{
echo " > update $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->update($table, $columns, $conditions, $params);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Creates and executes a DELETE SQL statement.
* @param string $table the table where the data will be deleted from.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please
* refer to {@link CDbCommand::where} on how to specify conditions.
* @param array $params the parameters to be bound to the query.
*/
public function delete($table, $conditions = '', $params = array())
{
echo " > delete from $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->delete($table, $conditions, $params);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for creating a new DB table.
*
* The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'),
* where name stands for a column name which will be properly quoted by the method, and definition
* stands for the column type which can contain an abstract DB type.
* The {@link getColumnType} method will be invoked to convert any abstract type into a physical one.
*
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
* inserted into the generated SQL.
*
* @param string $table the name of the table to be created. The name will be properly quoted by the method.
* @param array $columns the columns (name=>definition) in the new table.
* @param string $options additional SQL fragment that will be appended to the generated SQL.
*/
public function createTable($table, $columns, $options = null)
{
echo " > create table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->createTable($table, $columns, $options);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for renaming a DB table.
* @param string $table the table to be renamed. The name will be properly quoted by the method.
* @param string $newName the new table name. The name will be properly quoted by the method.
*/
public function renameTable($table, $newName)
{
echo " > rename table $table to $newName ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->renameTable($table, $newName);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for dropping a DB table.
* @param string $table the table to be dropped. The name will be properly quoted by the method.
*/
public function dropTable($table)
{
echo " > drop table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->dropTable($table);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for truncating a DB table.
* @param string $table the table to be truncated. The name will be properly quoted by the method.
*/
public function truncateTable($table)
{
echo " > truncate table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->truncateTable($table);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for adding a new DB column.
* @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
* @param string $column the name of the new column. The name will be properly quoted by the method.
* @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
*/
public function addColumn($table, $column, $type)
{
echo " > add column $column $type to table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->addColumn($table, $column, $type);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for dropping a DB column.
* @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
* @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
*/
public function dropColumn($table, $column)
{
echo " > drop column $column from table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->dropColumn($table, $column);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for renaming a column.
* @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
* @param string $name the old name of the column. The name will be properly quoted by the method.
* @param string $newName the new name of the column. The name will be properly quoted by the method.
*/
public function renameColumn($table, $name, $newName)
{
echo " > rename column $name in table $table to $newName ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->renameColumn($table, $name, $newName);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for changing the definition of a column.
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
* @param string $column the name of the column to be changed. The name will be properly quoted by the method.
* @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
*/
public function alterColumn($table, $column, $type)
{
echo " > alter column $column in table $table to $type ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->alterColumn($table, $column, $type);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds a SQL statement for adding a foreign key constraint to an existing table.
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
* @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
* @param string $refTable the table that the foreign key references to.
* @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{
echo " > add foreign key $name: $table ($columns) references $refTable ($refColumns) ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds a SQL statement for dropping a foreign key constraint.
* @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
*/
public function dropForeignKey($name, $table)
{
echo " > drop foreign key $name from table $table ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->dropForeignKey($name, $table);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
* @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
* by commas. The column names will be properly quoted by the method.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
*/
public function createIndex($name, $table, $column, $unique = false)
{
echo " > create" . ($unique ? ' unique' : '') . " index $name on $table ($column) ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->createIndex($name, $table, $column, $unique);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
/**
* Builds and executes a SQL statement for dropping an index.
* @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
*/
public function dropIndex($name, $table)
{
echo " > drop index $name ...";
$time = microtime(true);
$this->getDbConnection()->createCommand()->dropIndex($name, $table);
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
}
}
\ No newline at end of file
<?php
/**
* CActiveRecord class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CActiveRecord is the base class for classes representing relational data.
*
* It implements the active record design pattern, a popular Object-Relational Mapping (ORM) technique.
* Please check {@link http://www.yiiframework.com/doc/guide/database.ar the Guide} for more details
* about this class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*
* @property array $attributes
*/
abstract class CActiveRecord extends CModel
{
const BELONGS_TO = 'CBelongsToRelation';
const HAS_ONE = 'CHasOneRelation';
const HAS_MANY = 'CHasManyRelation';
const MANY_MANY = 'CManyManyRelation';
const STAT = 'CStatRelation';
/**
* @var CDbConnection the default database connection for all active record classes.
* By default, this is the 'db' application component.
* @see getDbConnection
*/
public static $db;
private static $_models = array(); // class name => model
private $_md; // meta data
private $_new = false; // whether this instance is new or not
private $_attributes = array(); // attribute name => attribute value
private $_related = array(); // attribute name => related objects
private $_c; // query criteria (used by finder only)
private $_pk; // old primary key value
private $_alias = 't'; // the table alias being used for query
/**
* Constructor.
* @param string $scenario scenario name. See {@link CModel::scenario} for more details about this parameter.
*/
public function __construct($scenario = 'insert')
{
if ($scenario === null) // internally used by populateRecord() and model()
return;
$this->setScenario($scenario);
$this->setIsNewRecord(true);
$this->_attributes = $this->getMetaData()->attributeDefaults;
$this->init();
$this->attachBehaviors($this->behaviors());
$this->afterConstruct();
}
/**
* Initializes this model.
* This method is invoked when an AR instance is newly created and has
* its {@link scenario} set.
* You may override this method to provide code that is needed to initialize the model (e.g. setting
* initial property values.)
* @since 1.0.8
*/
public function init()
{
}
/**
* Sets the parameters about query caching.
* This is a shortcut method to {@link CDbConnection::cache()}.
* It changes the query caching parameter of the {@link dbConnection} instance.
* @param integer $duration the number of seconds that query results may remain valid in cache.
* If this is 0, the caching will be disabled.
* @param CCacheDependency $dependency the dependency that will be used when saving the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
* @return CActiveRecord the active record instance itself.
* @since 1.1.7
*/
public function cache($duration, $dependency = null, $queryCount = 1)
{
$this->getDbConnection()->cache($duration, $dependency, $queryCount);
return $this;
}
/**
* PHP sleep magic method.
* This method ensures that the model meta data reference is set to null.
* @return array
*/
public function __sleep()
{
$this->_md = null;
return array_keys((array)$this);
}
/**
* PHP getter magic method.
* This method is overridden so that AR attributes can be accessed like properties.
* @param string $name property name
* @return mixed property value
* @see getAttribute
*/
public function __get($name)
{
if (isset($this->_attributes[$name]))
return $this->_attributes[$name];
elseif (isset($this->getMetaData()->columns[$name]))
return null;
elseif (isset($this->_related[$name]))
return $this->_related[$name];
elseif (isset($this->getMetaData()->relations[$name]))
return $this->getRelated($name);
else
return parent::__get($name);
}
/**
* PHP setter magic method.
* This method is overridden so that AR attributes can be accessed like properties.
* @param string $name property name
* @param mixed $value property value
*/
public function __set($name, $value)
{
if ($this->setAttribute($name, $value) === false)
{
if (isset($this->getMetaData()->relations[$name]))
$this->_related[$name] = $value;
else
parent::__set($name, $value);
}
}
/**
* Checks if a property value is null.
* This method overrides the parent implementation by checking
* if the named attribute is null or not.
* @param string $name the property name or the event name
* @return boolean whether the property value is null
* @since 1.0.1
*/
public function __isset($name)
{
if (isset($this->_attributes[$name]))
return true;
elseif (isset($this->getMetaData()->columns[$name]))
return false;
elseif (isset($this->_related[$name]))
return true;
elseif (isset($this->getMetaData()->relations[$name]))
return $this->getRelated($name) !== null;
else
return parent::__isset($name);
}
/**
* Sets a component property to be null.
* This method overrides the parent implementation by clearing
* the specified attribute value.
* @param string $name the property name or the event name
* @since 1.0.1
*/
public function __unset($name)
{
if (isset($this->getMetaData()->columns[$name]))
unset($this->_attributes[$name]);
elseif (isset($this->getMetaData()->relations[$name]))
unset($this->_related[$name]);
else
parent::__unset($name);
}
/**
* Calls the named method which is not a class method.
* Do not call this method. This is a PHP magic method that we override
* to implement the named scope feature.
* @param string $name the method name
* @param array $parameters method parameters
* @return mixed the method return value
* @since 1.0.5
*/
public function __call($name, $parameters)
{
if (isset($this->getMetaData()->relations[$name]))
{
if (empty($parameters))
return $this->getRelated($name, false);
else
return $this->getRelated($name, false, $parameters[0]);
}
$scopes = $this->scopes();
if (isset($scopes[$name]))
{
$this->getDbCriteria()->mergeWith($scopes[$name]);
return $this;
}
return parent::__call($name, $parameters);
}
/**
* Returns the related record(s).
* This method will return the related record(s) of the current record.
* If the relation is HAS_ONE or BELONGS_TO, it will return a single object
* or null if the object does not exist.
* If the relation is HAS_MANY or MANY_MANY, it will return an array of objects
* or an empty array.
* @param string $name the relation name (see {@link relations})
* @param boolean $refresh whether to reload the related objects from database. Defaults to false.
* @param array $params additional parameters that customize the query conditions as specified in the relation declaration.
* This parameter has been available since version 1.0.5.
* @return mixed the related object(s).
* @throws CDbException if the relation is not specified in {@link relations}.
* @since 1.0.2
*/
public function getRelated($name, $refresh = false, $params = array())
{
if (!$refresh && $params === array() && (isset($this->_related[$name]) || array_key_exists($name, $this->_related)))
return $this->_related[$name];
$md = $this->getMetaData();
if (!isset($md->relations[$name]))
throw new CDbException(Yii::t('yii', '{class} does not have relation "{name}".',
array('{class}' => get_class($this), '{name}' => $name)));
Yii::trace('lazy loading ' . get_class($this) . '.' . $name, 'system.db.ar.CActiveRecord');
$relation = $md->relations[$name];
if ($this->getIsNewRecord() && !$refresh && ($relation instanceof CHasOneRelation || $relation instanceof CHasManyRelation))
return $relation instanceof CHasOneRelation ? null : array();
if ($params !== array()) // dynamic query
{
$exists = isset($this->_related[$name]) || array_key_exists($name, $this->_related);
if ($exists)
$save = $this->_related[$name];
$r = array($name => $params);
}
else
$r = $name;
unset($this->_related[$name]);
$finder = new CActiveFinder($this, $r);
$finder->lazyFind($this);
if (!isset($this->_related[$name]))
{
if ($relation instanceof CHasManyRelation)
$this->_related[$name] = array();
elseif ($relation instanceof CStatRelation)
$this->_related[$name] = $relation->defaultValue;
else
$this->_related[$name] = null;
}
if ($params !== array())
{
$results = $this->_related[$name];
if ($exists)
$this->_related[$name] = $save;
else
unset($this->_related[$name]);
return $results;
}
else
return $this->_related[$name];
}
/**
* Returns a value indicating whether the named related object(s) has been loaded.
* @param string $name the relation name
* @return boolean a value indicating whether the named related object(s) has been loaded.
* @since 1.0.3
*/
public function hasRelated($name)
{
return isset($this->_related[$name]) || array_key_exists($name, $this->_related);
}
/**
* Returns the query criteria associated with this model.
* @param boolean $createIfNull whether to create a criteria instance if it does not exist. Defaults to true.
* @return CDbCriteria the query criteria that is associated with this model.
* This criteria is mainly used by {@link scopes named scope} feature to accumulate
* different criteria specifications.
* @since 1.0.5
*/
public function getDbCriteria($createIfNull = true)
{
if ($this->_c === null)
{
if (($c = $this->defaultScope()) !== array() || $createIfNull)
$this->_c = new CDbCriteria($c);
}
return $this->_c;
}
/**
* Sets the query criteria for the current model.
* @param CDbCriteria $criteria the query criteria
* @since 1.1.3
*/
public function setDbCriteria($criteria)
{
$this->_c = $criteria;
}
/**
* Returns the default named scope that should be implicitly applied to all queries for this model.
* Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.
* The default implementation simply returns an empty array. You may override this method
* if the model needs to be queried with some default criteria (e.g. only active records should be returned).
* @return array the query criteria. This will be used as the parameter to the constructor
* of {@link CDbCriteria}.
* @since 1.0.5
*/
public function defaultScope()
{
return array();
}
/**
* Resets all scopes and criterias applied including default scope.
*
* @return CActiveRecord
* @since 1.1.2
*/
public function resetScope()
{
$this->_c = new CDbCriteria();
return $this;
}
/**
* Returns the static model of the specified AR class.
* The model returned is a static instance of the AR class.
* It is provided for invoking class-level methods (something similar to static class methods.)
*
* EVERY derived AR class must override this method as follows,
* <pre>
* public static function model($className=__CLASS__)
* {
* return parent::model($className);
* }
* </pre>
*
* @param string $className active record class name.
* @return CActiveRecord active record model instance.
*/
public static function model($className = __CLASS__)
{
if (isset(self::$_models[$className]))
return self::$_models[$className];
else
{
$model = self::$_models[$className] = new $className(null);
$model->_md = new CActiveRecordMetaData($model);
$model->attachBehaviors($model->behaviors());
return $model;
}
}
/**
* Returns the meta-data for this AR
* @return CActiveRecordMetaData the meta for this AR class.
*/
public function getMetaData()
{
if ($this->_md !== null)
return $this->_md;
else
return $this->_md = self::model(get_class($this))->_md;
}
/**
* Refreshes the meta data for this AR class.
* By calling this method, this AR class will regenerate the meta data needed.
* This is useful if the table schema has been changed and you want to use the latest
* available table schema. Make sure you have called {@link CDbSchema::refresh}
* before you call this method. Otherwise, old table schema data will still be used.
* @since 1.0.8
*/
public function refreshMetaData()
{
$finder = self::model(get_class($this));
$finder->_md = new CActiveRecordMetaData($finder);
if ($this !== $finder)
$this->_md = $finder->_md;
}
/**
* Returns the name of the associated database table.
* By default this method returns the class name as the table name.
* You may override this method if the table is not named after this convention.
* @return string the table name
*/
public function tableName()
{
return get_class($this);
}
/**
* Returns the primary key of the associated database table.
* This method is meant to be overridden in case when the table is not defined with a primary key
* (for some legency database). If the table is already defined with a primary key,
* you do not need to override this method. The default implementation simply returns null,
* meaning using the primary key defined in the database.
* @return mixed the primary key of the associated database table.
* If the key is a single column, it should return the column name;
* If the key is a composite one consisting of several columns, it should
* return the array of the key column names.
* @since 1.0.4
*/
public function primaryKey()
{
}
/**
* This method should be overridden to declare related objects.
*
* There are four types of relations that may exist between two active record objects:
* <ul>
* <li>BELONGS_TO: e.g. a member belongs to a team;</li>
* <li>HAS_ONE: e.g. a member has at most one profile;</li>
* <li>HAS_MANY: e.g. a team has many members;</li>
* <li>MANY_MANY: e.g. a member has many skills and a skill belongs to a member.</li>
* </ul>
*
* Besides the above relation types, a special relation called STAT is also supported
* that can be used to perform statistical query (or aggregational query).
* It retrieves the aggregational information about the related objects, such as the number
* of comments for each post, the average rating for each product, etc.
*
* Each kind of related objects is defined in this method as an array with the following elements:
* <pre>
* 'varName'=>array('relationType', 'className', 'foreign_key', ...additional options)
* </pre>
* where 'varName' refers to the name of the variable/property that the related object(s) can
* be accessed through; 'relationType' refers to the type of the relation, which can be one of the
* following four constants: self::BELONGS_TO, self::HAS_ONE, self::HAS_MANY and self::MANY_MANY;
* 'className' refers to the name of the active record class that the related object(s) is of;
* and 'foreign_key' states the foreign key that relates the two kinds of active record.
* Note, for composite foreign keys, they must be listed together, separated by commas;
* and for foreign keys used in MANY_MANY relation, the joining table must be declared as well
* (e.g. 'join_table(fk1, fk2)').
*
* Additional options may be specified as name-value pairs in the rest array elements:
* <ul>
* <li>'select': string|array, a list of columns to be selected. Defaults to '*', meaning all columns.
* Column names should be disambiguated if they appear in an expression (e.g. COUNT(relationName.name) AS name_count).</li>
* <li>'condition': string, the WHERE clause. Defaults to empty. Note, column references need to
* be disambiguated with prefix 'relationName.' (e.g. relationName.age&gt;20)</li>
* <li>'order': string, the ORDER BY clause. Defaults to empty. Note, column references need to
* be disambiguated with prefix 'relationName.' (e.g. relationName.age DESC)</li>
* <li>'with': string|array, a list of child related objects that should be loaded together with this object.
* Note, this is only honored by lazy loading, not eager loading.</li>
* <li>'joinType': type of join. Defaults to 'LEFT OUTER JOIN'.</li>
* <li>'alias': the alias for the table associated with this relationship.
* This option has been available since version 1.0.1. It defaults to null,
* meaning the table alias is the same as the relation name.</li>
* <li>'params': the parameters to be bound to the generated SQL statement.
* This should be given as an array of name-value pairs. This option has been
* available since version 1.0.3.</li>
* <li>'on': the ON clause. The condition specified here will be appended
* to the joining condition using the AND operator. This option has been
* available since version 1.0.2.</li>
* <li>'index': the name of the column whose values should be used as keys
* of the array that stores related objects. This option is only available to
* HAS_MANY and MANY_MANY relations. This option has been available since version 1.0.7.</li>
* <li>'scopes': scopes to apply. In case of a single scope can be used like 'scopes'=>'scopeName',
* in case of multiple scopes can be used like 'scopes'=>array('scopeName1','scopeName2').
* This option has been available since version 1.1.9.</li>
* </ul>
*
* The following options are available for certain relations when lazy loading:
* <ul>
* <li>'group': string, the GROUP BY clause. Defaults to empty. Note, column references need to
* be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
* <li>'having': string, the HAVING clause. Defaults to empty. Note, column references need to
* be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
* <li>'limit': limit of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
* <li>'offset': offset of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
* <li>'through': name of the model's relation that will be used as a bridge when getting related data. Can be set only for HAS_ONE and HAS_MANY. This option has been available since version 1.1.7.</li>
* </ul>
*
* Below is an example declaring related objects for 'Post' active record class:
* <pre>
* return array(
* 'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
* 'comments'=>array(self::HAS_MANY, 'Comment', 'post_id', 'with'=>'author', 'order'=>'create_time DESC'),
* 'tags'=>array(self::MANY_MANY, 'Tag', 'post_tag(post_id, tag_id)', 'order'=>'name'),
* );
* </pre>
*
* @return array list of related object declarations. Defaults to empty array.
*/
public function relations()
{
return array();
}
/**
* Returns the declaration of named scopes.
* A named scope represents a query criteria that can be chained together with
* other named scopes and applied to a query. This method should be overridden
* by child classes to declare named scopes for the particular AR classes.
* For example, the following code declares two named scopes: 'recently' and
* 'published'.
* <pre>
* return array(
* 'published'=>array(
* 'condition'=>'status=1',
* ),
* 'recently'=>array(
* 'order'=>'create_time DESC',
* 'limit'=>5,
* ),
* );
* </pre>
* If the above scopes are declared in a 'Post' model, we can perform the following
* queries:
* <pre>
* $posts=Post::model()->published()->findAll();
* $posts=Post::model()->published()->recently()->findAll();
* $posts=Post::model()->published()->with('comments')->findAll();
* </pre>
* Note that the last query is a relational query.
*
* @return array the scope definition. The array keys are scope names; the array
* values are the corresponding scope definitions. Each scope definition is represented
* as an array whose keys must be properties of {@link CDbCriteria}.
* @since 1.0.5
*/
public function scopes()
{
return array();
}
/**
* Returns the list of all attribute names of the model.
* This would return all column names of the table associated with this AR class.
* @return array list of attribute names.
* @since 1.0.1
*/
public function attributeNames()
{
return array_keys($this->getMetaData()->columns);
}
/**
* Returns the text label for the specified attribute.
* This method overrides the parent implementation by supporting
* returning the label defined in relational object.
* In particular, if the attribute name is in the form of "post.author.name",
* then this method will derive the label from the "author" relation's "name" attribute.
* @param string $attribute the attribute name
* @return string the attribute label
* @see generateAttributeLabel
* @since 1.1.4
*/
public function getAttributeLabel($attribute)
{
$labels = $this->attributeLabels();
if (isset($labels[$attribute]))
return $labels[$attribute];
elseif (strpos($attribute, '.') !== false)
{
$segs = explode('.', $attribute);
$name = array_pop($segs);
$model = $this;
foreach ($segs as $seg)
{
$relations = $model->getMetaData()->relations;
if (isset($relations[$seg]))
$model = CActiveRecord::model($relations[$seg]->className);
else
break;
}
return $model->getAttributeLabel($name);
}
else
return $this->generateAttributeLabel($attribute);
}
/**
* Returns the database connection used by active record.
* By default, the "db" application component is used as the database connection.
* You may override this method if you want to use a different database connection.
* @return CDbConnection the database connection used by active record.
*/
public function getDbConnection()
{
if (self::$db !== null)
return self::$db;
else
{
self::$db = Yii::app()->getDb();
if (self::$db instanceof CDbConnection)
return self::$db;
else
throw new CDbException(Yii::t('yii', 'Active Record requires a "db" CDbConnection application component.'));
}
}
/**
* Returns the named relation declared for this AR class.
* @param string $name the relation name
* @return CActiveRelation the named relation declared for this AR class. Null if the relation does not exist.
*/
public function getActiveRelation($name)
{
return isset($this->getMetaData()->relations[$name]) ? $this->getMetaData()->relations[$name] : null;
}
/**
* Returns the metadata of the table that this AR belongs to
* @return CDbTableSchema the metadata of the table that this AR belongs to
*/
public function getTableSchema()
{
return $this->getMetaData()->tableSchema;
}
/**
* Returns the command builder used by this AR.
* @return CDbCommandBuilder the command builder used by this AR
*/
public function getCommandBuilder()
{
return $this->getDbConnection()->getSchema()->getCommandBuilder();
}
/**
* Checks whether this AR has the named attribute
* @param string $name attribute name
* @return boolean whether this AR has the named attribute (table column).
*/
public function hasAttribute($name)
{
return isset($this->getMetaData()->columns[$name]);
}
/**
* Returns the named attribute value.
* If this is a new record and the attribute is not set before,
* the default column value will be returned.
* If this record is the result of a query and the attribute is not loaded,
* null will be returned.
* You may also use $this->AttributeName to obtain the attribute value.
* @param string $name the attribute name
* @return mixed the attribute value. Null if the attribute is not set or does not exist.
* @see hasAttribute
*/
public function getAttribute($name)
{
if (property_exists($this, $name))
return $this->$name;
elseif (isset($this->_attributes[$name]))
return $this->_attributes[$name];
}
/**
* Sets the named attribute value.
* You may also use $this->AttributeName to set the attribute value.
* @param string $name the attribute name
* @param mixed $value the attribute value.
* @return boolean whether the attribute exists and the assignment is conducted successfully
* @see hasAttribute
*/
public function setAttribute($name, $value)
{
if (property_exists($this, $name))
$this->$name = $value;
elseif (isset($this->getMetaData()->columns[$name]))
$this->_attributes[$name] = $value;
else
return false;
return true;
}
/**
* Do not call this method. This method is used internally by {@link CActiveFinder} to populate
* related objects. This method adds a related object to this record.
* @param string $name attribute name
* @param mixed $record the related record
* @param mixed $index the index value in the related object collection.
* If true, it means using zero-based integer index.
* If false, it means a HAS_ONE or BELONGS_TO object and no index is needed.
*/
public function addRelatedRecord($name, $record, $index)
{
if ($index !== false)
{
if (!isset($this->_related[$name]))
$this->_related[$name] = array();
if ($record instanceof CActiveRecord)
{
if ($index === true)
$this->_related[$name][] = $record;
else
$this->_related[$name][$index] = $record;
}
}
elseif (!isset($this->_related[$name]))
$this->_related[$name] = $record;
}
/**
* Returns all column attribute values.
* Note, related objects are not returned.
* @param mixed $names names of attributes whose value needs to be returned.
* If this is true (default), then all attribute values will be returned, including
* those that are not loaded from DB (null will be returned for those attributes).
* If this is null, all attributes except those that are not loaded from DB will be returned.
* @return array attribute values indexed by attribute names.
*/
public function getAttributes($names = true)
{
$attributes = $this->_attributes;
foreach ($this->getMetaData()->columns as $name => $column)
{
if (property_exists($this, $name))
$attributes[$name] = $this->$name;
elseif ($names === true && !isset($attributes[$name]))
$attributes[$name] = null;
}
if (is_array($names))
{
$attrs = array();
foreach ($names as $name)
{
if (property_exists($this, $name))
$attrs[$name] = $this->$name;
else
$attrs[$name] = isset($attributes[$name]) ? $attributes[$name] : null;
}
return $attrs;
}
else
return $attributes;
}
/**
* Saves the current record.
*
* The record is inserted as a row into the database table if its {@link isNewRecord}
* property is true (usually the case when the record is created using the 'new'
* operator). Otherwise, it will be used to update the corresponding row in the table
* (usually the case if the record is obtained using one of those 'find' methods.)
*
* Validation will be performed before saving the record. If the validation fails,
* the record will not be saved. You can call {@link getErrors()} to retrieve the
* validation errors.
*
* If the record is saved via insertion, its {@link isNewRecord} property will be
* set false, and its {@link scenario} property will be set to be 'update'.
* And if its primary key is auto-incremental and is not set before insertion,
* the primary key will be populated with the automatically generated key value.
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be saved to database.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the saving succeeds
*/
public function save($runValidation = true, $attributes = null)
{
if (!$runValidation || $this->validate($attributes))
return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
else
return false;
}
/**
* Returns if the current record is new.
* @return boolean whether the record is new and should be inserted when calling {@link save}.
* This property is automatically set in constructor and {@link populateRecord}.
* Defaults to false, but it will be set to true if the instance is created using
* the new operator.
*/
public function getIsNewRecord()
{
return $this->_new;
}
/**
* Sets if the record is new.
* @param boolean $value whether the record is new and should be inserted when calling {@link save}.
* @see getIsNewRecord
*/
public function setIsNewRecord($value)
{
$this->_new = $value;
}
/**
* This event is raised before the record is saved.
* By setting {@link CModelEvent::isValid} to be false, the normal {@link save()} process will be stopped.
* @param CModelEvent $event the event parameter
* @since 1.0.2
*/
public function onBeforeSave($event)
{
$this->raiseEvent('onBeforeSave', $event);
}
/**
* This event is raised after the record is saved.
* @param CEvent $event the event parameter
* @since 1.0.2
*/
public function onAfterSave($event)
{
$this->raiseEvent('onAfterSave', $event);
}
/**
* This event is raised before the record is deleted.
* By setting {@link CModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped.
* @param CModelEvent $event the event parameter
* @since 1.0.2
*/
public function onBeforeDelete($event)
{
$this->raiseEvent('onBeforeDelete', $event);
}
/**
* This event is raised after the record is deleted.
* @param CEvent $event the event parameter
* @since 1.0.2
*/
public function onAfterDelete($event)
{
$this->raiseEvent('onAfterDelete', $event);
}
/**
* This event is raised before an AR finder performs a find call.
* In this event, the {@link CModelEvent::criteria} property contains the query criteria
* passed as parameters to those find methods. If you want to access
* the query criteria specified in scopes, please use {@link getDbCriteria()}.
* You can modify either criteria to customize them based on needs.
* @param CModelEvent $event the event parameter
* @see beforeFind
* @since 1.0.9
*/
public function onBeforeFind($event)
{
$this->raiseEvent('onBeforeFind', $event);
}
/**
* This event is raised after the record is instantiated by a find method.
* @param CEvent $event the event parameter
* @since 1.0.2
*/
public function onAfterFind($event)
{
$this->raiseEvent('onAfterFind', $event);
}
/**
* This method is invoked before saving a record (after validation, if any).
* The default implementation raises the {@link onBeforeSave} event.
* You may override this method to do any preparation work for record saving.
* Use {@link isNewRecord} to determine whether the saving is
* for inserting or updating record.
* Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the saving should be executed. Defaults to true.
*/
protected function beforeSave()
{
if ($this->hasEventHandler('onBeforeSave'))
{
$event = new CModelEvent($this);
$this->onBeforeSave($event);
return $event->isValid;
}
else
return true;
}
/**
* This method is invoked after saving a record successfully.
* The default implementation raises the {@link onAfterSave} event.
* You may override this method to do postprocessing after record saving.
* Make sure you call the parent implementation so that the event is raised properly.
*/
protected function afterSave()
{
if ($this->hasEventHandler('onAfterSave'))
$this->onAfterSave(new CEvent($this));
}
/**
* This method is invoked before deleting a record.
* The default implementation raises the {@link onBeforeDelete} event.
* You may override this method to do any preparation work for record deletion.
* Make sure you call the parent implementation so that the event is raised properly.
* @return boolean whether the record should be deleted. Defaults to true.
*/
protected function beforeDelete()
{
if ($this->hasEventHandler('onBeforeDelete'))
{
$event = new CModelEvent($this);
$this->onBeforeDelete($event);
return $event->isValid;
}
else
return true;
}
/**
* This method is invoked after deleting a record.
* The default implementation raises the {@link onAfterDelete} event.
* You may override this method to do postprocessing after the record is deleted.
* Make sure you call the parent implementation so that the event is raised properly.
*/
protected function afterDelete()
{
if ($this->hasEventHandler('onAfterDelete'))
$this->onAfterDelete(new CEvent($this));
}
/**
* This method is invoked before an AR finder executes a find call.
* The find calls include {@link find}, {@link findAll}, {@link findByPk},
* {@link findAllByPk}, {@link findByAttributes} and {@link findAllByAttributes}.
* The default implementation raises the {@link onBeforeFind} event.
* If you override this method, make sure you call the parent implementation
* so that the event is raised properly.
*
* Starting from version 1.1.5, this method may be called with a hidden {@link CDbCriteria}
* parameter which represents the current query criteria as passed to a find method of AR.
*
* @since 1.0.9
*/
protected function beforeFind()
{
if ($this->hasEventHandler('onBeforeFind'))
{
$event = new CModelEvent($this);
// for backward compatibility
$event->criteria = func_num_args() > 0 ? func_get_arg(0) : null;
$this->onBeforeFind($event);
}
}
/**
* This method is invoked after each record is instantiated by a find method.
* The default implementation raises the {@link onAfterFind} event.
* You may override this method to do postprocessing after each newly found record is instantiated.
* Make sure you call the parent implementation so that the event is raised properly.
*/
protected function afterFind()
{
if ($this->hasEventHandler('onAfterFind'))
$this->onAfterFind(new CEvent($this));
}
/**
* Calls {@link beforeFind}.
* This method is internally used.
* @since 1.0.11
*/
public function beforeFindInternal()
{
$this->beforeFind();
}
/**
* Calls {@link afterFind}.
* This method is internally used.
* @since 1.0.3
*/
public function afterFindInternal()
{
$this->afterFind();
}
/**
* Inserts a row into the table based on this active record attributes.
* If the table's primary key is auto-incremental and is null before insertion,
* it will be populated with the actual value after insertion.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
* and its {@link scenario} property will be set to be 'update'.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws CException if the record is not new
*/
public function insert($attributes = null)
{
if (!$this->getIsNewRecord())
throw new CDbException(Yii::t('yii', 'The active record cannot be inserted to database because it is not new.'));
if ($this->beforeSave())
{
Yii::trace(get_class($this) . '.insert()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$table = $this->getMetaData()->tableSchema;
$command = $builder->createInsertCommand($table, $this->getAttributes($attributes));
if ($command->execute())
{
$primaryKey = $table->primaryKey;
if ($table->sequenceName !== null)
{
if (is_string($primaryKey) && $this->$primaryKey === null)
$this->$primaryKey = $builder->getLastInsertID($table);
elseif (is_array($primaryKey))
{
foreach ($primaryKey as $pk)
{
if ($this->$pk === null)
{
$this->$pk = $builder->getLastInsertID($table);
break;
}
}
}
}
$this->_pk = $this->getPrimaryKey();
$this->afterSave();
$this->setIsNewRecord(false);
$this->setScenario('update');
return true;
}
}
return false;
}
/**
* Updates the row represented by this active record.
* All loaded attributes will be saved to the database.
* Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* @return boolean whether the update is successful
* @throws CException if the record is new
*/
public function update($attributes = null)
{
if ($this->getIsNewRecord())
throw new CDbException(Yii::t('yii', 'The active record cannot be updated because it is new.'));
if ($this->beforeSave())
{
Yii::trace(get_class($this) . '.update()', 'system.db.ar.CActiveRecord');
if ($this->_pk === null)
$this->_pk = $this->getPrimaryKey();
$this->updateByPk($this->getOldPrimaryKey(), $this->getAttributes($attributes));
$this->_pk = $this->getPrimaryKey();
$this->afterSave();
return true;
}
else
return false;
}
/**
* Saves a selected list of attributes.
* Unlike {@link save}, this method only saves the specified attributes
* of an existing row dataset and does NOT call either {@link beforeSave} or {@link afterSave}.
* Also note that this method does neither attribute filtering nor validation.
* So do not use this method with untrusted data (such as user posted data).
* You may consider the following alternative if you want to do so:
* <pre>
* $postRecord=Post::model()->findByPk($postID);
* $postRecord->attributes=$_POST['post'];
* $postRecord->save();
* </pre>
* @param array $attributes attributes to be updated. Each element represents an attribute name
* or an attribute value indexed by its name. If the latter, the record's
* attribute will be changed accordingly before saving.
* @return boolean whether the update is successful
* @throws CException if the record is new or any database error
*/
public function saveAttributes($attributes)
{
if (!$this->getIsNewRecord())
{
Yii::trace(get_class($this) . '.saveAttributes()', 'system.db.ar.CActiveRecord');
$values = array();
foreach ($attributes as $name => $value)
{
if (is_integer($name))
$values[$value] = $this->$value;
else
$values[$name] = $this->$name = $value;
}
if ($this->_pk === null)
$this->_pk = $this->getPrimaryKey();
if ($this->updateByPk($this->getOldPrimaryKey(), $values) > 0)
{
$this->_pk = $this->getPrimaryKey();
return true;
}
else
return false;
}
else
throw new CDbException(Yii::t('yii', 'The active record cannot be updated because it is new.'));
}
/**
* Saves one or several counter columns for the current AR object.
* Note that this method differs from {@link updateCounters} in that it only
* saves the current AR object.
* An example usage is as follows:
* <pre>
* $postRecord=Post::model()->findByPk($postID);
* $postRecord->saveCounters(array('view_count'=>1));
* </pre>
* Use negative values if you want to decrease the counters.
* @param array $counters the counters to be updated (column name=>increment value)
* @return boolean whether the saving is successful
* @see updateCounters
* @since 1.1.8
*/
public function saveCounters($counters)
{
Yii::trace(get_class($this) . '.saveCounters()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$table = $this->getTableSchema();
$criteria = $builder->createPkCriteria($table, $this->getOldPrimaryKey());
$command = $builder->createUpdateCounterCommand($this->getTableSchema(), $counters, $criteria);
if ($command->execute())
{
foreach ($counters as $name => $value)
$this->$name = $this->$name + $value;
return true;
}
else
return false;
}
/**
* Deletes the row corresponding to this active record.
* @return boolean whether the deletion is successful.
* @throws CException if the record is new
*/
public function delete()
{
if (!$this->getIsNewRecord())
{
Yii::trace(get_class($this) . '.delete()', 'system.db.ar.CActiveRecord');
if ($this->beforeDelete())
{
$result = $this->deleteByPk($this->getPrimaryKey()) > 0;
$this->afterDelete();
return $result;
}
else
return false;
}
else
throw new CDbException(Yii::t('yii', 'The active record cannot be deleted because it is new.'));
}
/**
* Repopulates this active record with the latest data.
* @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
*/
public function refresh()
{
Yii::trace(get_class($this) . '.refresh()', 'system.db.ar.CActiveRecord');
if (!$this->getIsNewRecord() && ($record = $this->findByPk($this->getPrimaryKey())) !== null)
{
$this->_attributes = array();
$this->_related = array();
foreach ($this->getMetaData()->columns as $name => $column)
{
if (property_exists($this, $name))
$this->$name = $record->$name;
else
$this->_attributes[$name] = $record->$name;
}
return true;
}
else
return false;
}
/**
* Compares current active record with another one.
* The comparison is made by comparing table name and the primary key values of the two active records.
* @param CActiveRecord $record record to compare to
* @return boolean whether the two active records refer to the same row in the database table.
*/
public function equals($record)
{
return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
}
/**
* Returns the primary key value.
* @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite.
* If primary key is not defined, null will be returned.
*/
public function getPrimaryKey()
{
$table = $this->getMetaData()->tableSchema;
if (is_string($table->primaryKey))
return $this-> {$table->primaryKey};
elseif (is_array($table->primaryKey))
{
$values = array();
foreach ($table->primaryKey as $name)
$values[$name] = $this->$name;
return $values;
}
else
return null;
}
/**
* Sets the primary key value.
* After calling this method, the old primary key value can be obtained from {@link oldPrimaryKey}.
* @param mixed $value the new primary key value. If the primary key is composite, the new value
* should be provided as an array (column name=>column value).
* @since 1.1.0
*/
public function setPrimaryKey($value)
{
$this->_pk = $this->getPrimaryKey();
$table = $this->getMetaData()->tableSchema;
if (is_string($table->primaryKey))
$this-> {$table->primaryKey} = $value;
elseif (is_array($table->primaryKey))
{
foreach ($table->primaryKey as $name)
$this->$name = $value[$name];
}
}
/**
* Returns the old primary key value.
* This refers to the primary key value that is populated into the record
* after executing a find method (e.g. find(), findAll()).
* The value remains unchanged even if the primary key attribute is manually assigned with a different value.
* @return mixed the old primary key value. An array (column name=>column value) is returned if the primary key is composite.
* If primary key is not defined, null will be returned.
* @since 1.1.0
*/
public function getOldPrimaryKey()
{
return $this->_pk;
}
/**
* Sets the old primary key value.
* @param mixed $value the old primary key value.
* @since 1.1.3
*/
public function setOldPrimaryKey($value)
{
$this->_pk = $value;
}
/**
* Performs the actual DB query and populates the AR objects with the query result.
* This method is mainly internally used by other AR query methods.
* @param CDbCriteria $criteria the query criteria
* @param boolean $all whether to return all data
* @return mixed the AR objects populated with the query result
* @since 1.1.7
*/
protected function query($criteria, $all = false)
{
$this->beforeFind();
$this->applyScopes($criteria);
if (empty($criteria->with))
{
if (!$all)
$criteria->limit = 1;
$command = $this->getCommandBuilder()->createFindCommand($this->getTableSchema(), $criteria);
return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow());
}
else
{
$finder = new CActiveFinder($this, $criteria->with);
return $finder->query($criteria, $all);
}
}
/**
* Applies the query scopes to the given criteria.
* This method merges {@link dbCriteria} with the given criteria parameter.
* It then resets {@link dbCriteria} to be null.
* @param CDbCriteria $criteria the query criteria. This parameter may be modified by merging {@link dbCriteria}.
* @since 1.0.12
*/
public function applyScopes(&$criteria)
{
if (!empty($criteria->scopes))
{
$scs = $this->scopes();
$c = $this->getDbCriteria();
foreach ((array)$criteria->scopes as $k => $v)
{
if (is_integer($k))
{
if (is_string($v))
{
if (isset($scs[$v]))
{
$c->mergeWith($scs[$v], true);
continue;
}
$scope = $v;
$params = array();
}
elseif (is_array($v))
{
$scope = key($v);
$params = current($v);
}
}
elseif (is_string($k))
{
$scope = $k;
$params = $v;
}
call_user_func_array(array($this, $scope), (array)$params);
}
}
if (isset($c) || ($c = $this->getDbCriteria(false)) !== null)
{
$c->mergeWith($criteria);
$criteria = $c;
$this->_c = null;
}
}
/**
* Returns the table alias to be used by the find methods.
* In relational queries, the returned table alias may vary according to
* the corresponding relation declaration. Also, the default table alias
* set by {@link setTableAlias} may be overridden by the applied scopes.
* @param boolean $quote whether to quote the alias name
* @param boolean $checkScopes whether to check if a table alias is defined in the applied scopes so far.
* This parameter must be set false when calling this method in {@link defaultScope}.
* An infinite loop would be formed otherwise.
* @return string the default table alias
* @since 1.1.1
*/
public function getTableAlias($quote = false, $checkScopes = true)
{
if ($checkScopes && ($criteria = $this->getDbCriteria(false)) !== null && $criteria->alias != '')
$alias = $criteria->alias;
else
$alias = $this->_alias;
return $quote ? $this->getDbConnection()->getSchema()->quoteTableName($alias) : $alias;
}
/**
* Sets the table alias to be used in queries.
* @param string $alias the table alias to be used in queries. The alias should NOT be quoted.
* @since 1.1.3
*/
public function setTableAlias($alias)
{
$this->_alias = $alias;
}
/**
* Finds a single active record with the specified condition.
* @param mixed $condition query condition or criteria.
* If a string, it is treated as query condition (the WHERE clause);
* If an array, it is treated as the initial values for constructing a {@link CDbCriteria} object;
* Otherwise, it should be an instance of {@link CDbCriteria}.
* @param array $params parameters to be bound to an SQL statement.
* This is only used when the first parameter is a string (query condition).
* In other cases, please use {@link CDbCriteria::params} to set parameters.
* @return CActiveRecord the record found. Null if no record is found.
*/
public function find($condition = '', $params = array())
{
Yii::trace(get_class($this) . '.find()', 'system.db.ar.CActiveRecord');
$criteria = $this->getCommandBuilder()->createCriteria($condition, $params);
return $this->query($criteria);
}
/**
* Finds all active records satisfying the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return array list of active records satisfying the specified condition. An empty array is returned if none is found.
*/
public function findAll($condition = '', $params = array())
{
Yii::trace(get_class($this) . '.findAll()', 'system.db.ar.CActiveRecord');
$criteria = $this->getCommandBuilder()->createCriteria($condition, $params);
return $this->query($criteria, true);
}
/**
* Finds a single active record with the specified primary key.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return CActiveRecord the record found. Null if none is found.
*/
public function findByPk($pk, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.findByPk()', 'system.db.ar.CActiveRecord');
$prefix = $this->getTableAlias(true) . '.';
$criteria = $this->getCommandBuilder()->createPkCriteria($this->getTableSchema(), $pk, $condition, $params, $prefix);
return $this->query($criteria);
}
/**
* Finds all active records with the specified primary keys.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return array the records found. An empty array is returned if none is found.
*/
public function findAllByPk($pk, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.findAllByPk()', 'system.db.ar.CActiveRecord');
$prefix = $this->getTableAlias(true) . '.';
$criteria = $this->getCommandBuilder()->createPkCriteria($this->getTableSchema(), $pk, $condition, $params, $prefix);
return $this->query($criteria, true);
}
/**
* Finds a single active record that has the specified attribute values.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* Since version 1.0.8, an attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return CActiveRecord the record found. Null if none is found.
*/
public function findByAttributes($attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.findByAttributes()', 'system.db.ar.CActiveRecord');
$prefix = $this->getTableAlias(true) . '.';
$criteria = $this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix);
return $this->query($criteria);
}
/**
* Finds all active records that have the specified attribute values.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* Since version 1.0.8, an attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return array the records found. An empty array is returned if none is found.
*/
public function findAllByAttributes($attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.findAllByAttributes()', 'system.db.ar.CActiveRecord');
$prefix = $this->getTableAlias(true) . '.';
$criteria = $this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix);
return $this->query($criteria, true);
}
/**
* Finds a single active record with the specified SQL statement.
* @param string $sql the SQL statement
* @param array $params parameters to be bound to the SQL statement
* @return CActiveRecord the record found. Null if none is found.
*/
public function findBySql($sql, $params = array())
{
Yii::trace(get_class($this) . '.findBySql()', 'system.db.ar.CActiveRecord');
$this->beforeFind();
if (($criteria = $this->getDbCriteria(false)) !== null && !empty($criteria->with))
{
$this->_c = null;
$finder = new CActiveFinder($this, $criteria->with);
return $finder->findBySql($sql, $params);
}
else
{
$command = $this->getCommandBuilder()->createSqlCommand($sql, $params);
return $this->populateRecord($command->queryRow());
}
}
/**
* Finds all active records using the specified SQL statement.
* @param string $sql the SQL statement
* @param array $params parameters to be bound to the SQL statement
* @return array the records found. An empty array is returned if none is found.
*/
public function findAllBySql($sql, $params = array())
{
Yii::trace(get_class($this) . '.findAllBySql()', 'system.db.ar.CActiveRecord');
$this->beforeFind();
if (($criteria = $this->getDbCriteria(false)) !== null && !empty($criteria->with))
{
$this->_c = null;
$finder = new CActiveFinder($this, $criteria->with);
return $finder->findAllBySql($sql, $params);
}
else
{
$command = $this->getCommandBuilder()->createSqlCommand($sql, $params);
return $this->populateRecords($command->queryAll());
}
}
/**
* Finds the number of rows satisfying the specified query condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return string the number of rows satisfying the specified query condition. Note: type is string to keep max. precision.
*/
public function count($condition = '', $params = array())
{
Yii::trace(get_class($this) . '.count()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createCriteria($condition, $params);
$this->applyScopes($criteria);
if (empty($criteria->with))
return $builder->createCountCommand($this->getTableSchema(), $criteria)->queryScalar();
else
{
$finder = new CActiveFinder($this, $criteria->with);
return $finder->count($criteria);
}
}
/**
* Finds the number of rows that have the specified attribute values.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* An attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return string the number of rows satisfying the specified query condition. Note: type is string to keep max. precision.
* @since 1.1.4
*/
public function countByAttributes($attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.countByAttributes()', 'system.db.ar.CActiveRecord');
$prefix = $this->getTableAlias(true) . '.';
$builder = $this->getCommandBuilder();
$criteria = $builder->createColumnCriteria($this->getTableSchema(), $attributes, $condition, $params, $prefix);
$this->applyScopes($criteria);
if (empty($criteria->with))
return $builder->createCountCommand($this->getTableSchema(), $criteria)->queryScalar();
else
{
$finder = new CActiveFinder($this, $criteria->with);
return $finder->count($criteria);
}
}
/**
* Finds the number of rows using the given SQL statement.
* This is equivalent to calling {@link CDbCommand::queryScalar} with the specified
* SQL statement and the parameters.
* @param string $sql the SQL statement
* @param array $params parameters to be bound to the SQL statement
* @return string the number of rows using the given SQL statement. Note: type is string to keep max. precision.
*/
public function countBySql($sql, $params = array())
{
Yii::trace(get_class($this) . '.countBySql()', 'system.db.ar.CActiveRecord');
return $this->getCommandBuilder()->createSqlCommand($sql, $params)->queryScalar();
}
/**
* Checks whether there is row satisfying the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return boolean whether there is row satisfying the specified condition.
*/
public function exists($condition = '', $params = array())
{
Yii::trace(get_class($this) . '.exists()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createCriteria($condition, $params);
$table = $this->getTableSchema();
$criteria->select = '1';
$criteria->limit = 1;
$this->applyScopes($criteria);
if (empty($criteria->with))
return $builder->createFindCommand($table, $criteria)->queryRow() !== false;
else
{
$criteria->select = '*';
$finder = new CActiveFinder($this, $criteria->with);
return $finder->count($criteria) > 0;
}
}
/**
* Specifies which related objects should be eagerly loaded.
* This method takes variable number of parameters. Each parameter specifies
* the name of a relation or child-relation. For example,
* <pre>
* // find all posts together with their author and comments
* Post::model()->with('author','comments')->findAll();
* // find all posts together with their author and the author's profile
* Post::model()->with('author','author.profile')->findAll();
* </pre>
* The relations should be declared in {@link relations()}.
*
* By default, the options specified in {@link relations()} will be used
* to do relational query. In order to customize the options on the fly,
* we should pass an array parameter to the with() method. The array keys
* are relation names, and the array values are the corresponding query options.
* For example,
* <pre>
* Post::model()->with(array(
* 'author'=>array('select'=>'id, name'),
* 'comments'=>array('condition'=>'approved=1', 'order'=>'create_time'),
* ))->findAll();
* </pre>
*
* Note, the possible parameters to this method have been changed since version 1.0.2.
* Previously, it was not possible to specify on-th-fly query options,
* and child-relations were specified as hierarchical arrays.
*
* @return CActiveRecord the AR object itself.
*/
public function with()
{
if (func_num_args() > 0)
{
$with = func_get_args();
if (is_array($with[0])) // the parameter is given as an array
$with = $with[0];
if (!empty($with))
$this->getDbCriteria()->mergeWith(array('with' => $with));
}
return $this;
}
/**
* Sets {@link CDbCriteria::together} property to be true.
* This is only used in relational AR query. Please refer to {@link CDbCriteria::together}
* for more details.
* @return CActiveRecord the AR object itself
* @since 1.1.4
*/
public function together()
{
$this->getDbCriteria()->together = true;
return $this;
}
/**
* Updates records with the specified primary key(s).
* See {@link find()} for detailed explanation about $condition and $params.
* Note, the attributes are not checked for safety and validation is NOT performed.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param array $attributes list of attributes (name=>$value) to be updated
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer the number of rows being updated
*/
public function updateByPk($pk, $attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.updateByPk()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$table = $this->getTableSchema();
$criteria = $builder->createPkCriteria($table, $pk, $condition, $params);
$command = $builder->createUpdateCommand($table, $attributes, $criteria);
return $command->execute();
}
/**
* Updates records with the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* Note, the attributes are not checked for safety and no validation is done.
* @param array $attributes list of attributes (name=>$value) to be updated
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer the number of rows being updated
*/
public function updateAll($attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.updateAll()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createCriteria($condition, $params);
$command = $builder->createUpdateCommand($this->getTableSchema(), $attributes, $criteria);
return $command->execute();
}
/**
* Updates one or several counter columns.
* Note, this updates all rows of data unless a condition or criteria is specified.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array $counters the counters to be updated (column name=>increment value)
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer the number of rows being updated
* @see saveCounters
*/
public function updateCounters($counters, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.updateCounters()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createCriteria($condition, $params);
$command = $builder->createUpdateCounterCommand($this->getTableSchema(), $counters, $criteria);
return $command->execute();
}
/**
* Deletes rows with the specified primary key.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer the number of rows deleted
*/
public function deleteByPk($pk, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.deleteByPk()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createPkCriteria($this->getTableSchema(), $pk, $condition, $params);
$command = $builder->createDeleteCommand($this->getTableSchema(), $criteria);
return $command->execute();
}
/**
* Deletes rows with the specified condition.
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer the number of rows deleted
*/
public function deleteAll($condition = '', $params = array())
{
Yii::trace(get_class($this) . '.deleteAll()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$criteria = $builder->createCriteria($condition, $params);
$command = $builder->createDeleteCommand($this->getTableSchema(), $criteria);
return $command->execute();
}
/**
* Deletes rows which match the specified attribute values.
* See {@link find()} for detailed explanation about $condition and $params.
* @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
* Since version 1.0.8, an attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
* @return integer number of rows affected by the execution.
* @since 1.0.9
*/
public function deleteAllByAttributes($attributes, $condition = '', $params = array())
{
Yii::trace(get_class($this) . '.deleteAllByAttributes()', 'system.db.ar.CActiveRecord');
$builder = $this->getCommandBuilder();
$table = $this->getTableSchema();
$criteria = $builder->createColumnCriteria($table, $attributes, $condition, $params);
$command = $builder->createDeleteCommand($table, $criteria);
return $command->execute();
}
/**
* Creates an active record with the given attributes.
* This method is internally used by the find methods.
* @param array $attributes attribute values (column name=>column value)
* @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated.
* This parameter is added in version 1.0.3.
* @return CActiveRecord the newly created active record. The class of the object is the same as the model class.
* Null is returned if the input data is false.
*/
public function populateRecord($attributes, $callAfterFind = true)
{
if ($attributes !== false)
{
$record = $this->instantiate($attributes);
$record->setScenario('update');
$record->init();
$md = $record->getMetaData();
foreach ($attributes as $name => $value)
{
if (property_exists($record, $name))
$record->$name = $value;
elseif (isset($md->columns[$name]))
$record->_attributes[$name] = $value;
}
$record->_pk = $record->getPrimaryKey();
$record->attachBehaviors($record->behaviors());
if ($callAfterFind)
$record->afterFind();
return $record;
}
else
return null;
}
/**
* Creates a list of active records based on the input data.
* This method is internally used by the find methods.
* @param array $data list of attribute values for the active records.
* @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated.
* This parameter is added in version 1.0.3.
* @param string $index the name of the attribute whose value will be used as indexes of the query result array.
* If null, it means the array will be indexed by zero-based integers.
* @return array list of active records.
*/
public function populateRecords($data, $callAfterFind = true, $index = null)
{
$records = array();
foreach ($data as $attributes)
{
if (($record = $this->populateRecord($attributes, $callAfterFind)) !== null)
{
if ($index === null)
$records[] = $record;
else
$records[$record->$index] = $record;
}
}
return $records;
}
/**
* Creates an active record instance.
* This method is called by {@link populateRecord} and {@link populateRecords}.
* You may override this method if the instance being created
* depends the attributes that are to be populated to the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $attributes list of attribute values for the active records.
* @return CActiveRecord the active record
* @since 1.0.2
*/
protected function instantiate($attributes)
{
$class = get_class($this);
$model = new $class(null);
return $model;
}
/**
* Returns whether there is an element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean
* @since 1.0.2
*/
public function offsetExists($offset)
{
return $this->__isset($offset);
}
}
/**
* CBaseActiveRelation is the base class for all active relations.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0.4
*/
class CBaseActiveRelation extends CComponent
{
/**
* @var string name of the related object
*/
public $name;
/**
* @var string name of the related active record class
*/
public $className;
/**
* @var string the foreign key in this relation
*/
public $foreignKey;
/**
* @var mixed list of column names (an array, or a string of names separated by commas) to be selected.
* Do not quote or prefix the column names unless they are used in an expression.
* In that case, you should prefix the column names with 'relationName.'.
*/
public $select = '*';
/**
* @var string WHERE clause. For {@link CActiveRelation} descendant classes, column names
* referenced in the condition should be disambiguated with prefix 'relationName.'.
*/
public $condition = '';
/**
* @var array the parameters that are to be bound to the condition.
* The keys are parameter placeholder names, and the values are parameter values.
*/
public $params = array();
/**
* @var string GROUP BY clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $group = '';
/**
* @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
* For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
* @since 1.1.3
*/
public $join = '';
/**
* @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $having = '';
/**
* @var string ORDER BY clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
*/
public $order = '';
/**
* Constructor.
* @param string $name name of the relation
* @param string $className name of the related active record class
* @param string $foreignKey foreign key for this relation
* @param array $options additional options (name=>value). The keys must be the property names of this class.
*/
public function __construct($name, $className, $foreignKey, $options = array())
{
$this->name = $name;
$this->className = $className;
$this->foreignKey = $foreignKey;
foreach ($options as $name => $value)
$this->$name = $value;
}
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
* @since 1.0.5
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria)
$criteria = $criteria->toArray();
if (isset($criteria['select']) && $this->select !== $criteria['select'])
{
if ($this->select === '*')
$this->select = $criteria['select'];
elseif ($criteria['select'] !== '*')
{
$select1 = is_string($this->select) ? preg_split('/\s*,\s*/', trim($this->select), -1, PREG_SPLIT_NO_EMPTY) : $this->select;
$select2 = is_string($criteria['select']) ? preg_split('/\s*,\s*/', trim($criteria['select']), -1, PREG_SPLIT_NO_EMPTY) : $criteria['select'];
$this->select = array_merge($select1, array_diff($select2, $select1));
}
}
if (isset($criteria['condition']) && $this->condition !== $criteria['condition'])
{
if ($this->condition === '')
$this->condition = $criteria['condition'];
elseif ($criteria['condition'] !== '')
$this->condition = "( {$this->condition}) AND ( {$criteria['condition']})";
}
if (isset($criteria['params']) && $this->params !== $criteria['params'])
$this->params = array_merge($this->params, $criteria['params']);
if (isset($criteria['order']) && $this->order !== $criteria['order'])
{
if ($this->order === '')
$this->order = $criteria['order'];
elseif ($criteria['order'] !== '')
$this->order = $criteria['order'] . ', ' . $this->order;
}
if (isset($criteria['group']) && $this->group !== $criteria['group'])
{
if ($this->group === '')
$this->group = $criteria['group'];
elseif ($criteria['group'] !== '')
$this->group .= ', ' . $criteria['group'];
}
if (isset($criteria['join']) && $this->join !== $criteria['join'])
{
if ($this->join === '')
$this->join = $criteria['join'];
elseif ($criteria['join'] !== '')
$this->join .= ' ' . $criteria['join'];
}
if (isset($criteria['having']) && $this->having !== $criteria['having'])
{
if ($this->having === '')
$this->having = $criteria['having'];
elseif ($criteria['having'] !== '')
$this->having = "( {$this->having}) AND ( {$criteria['having']})";
}
}
}
/**
* CStatRelation represents a statistical relational query.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0.4
*/
class CStatRelation extends CBaseActiveRelation
{
/**
* @var string the statistical expression. Defaults to 'COUNT(*)', meaning
* the count of child objects.
*/
public $select = 'COUNT(*)';
/**
* @var mixed the default value to be assigned to those records that do not
* receive a statistical query result. Defaults to 0.
*/
public $defaultValue = 0;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
* @since 1.0.5
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria)
$criteria = $criteria->toArray();
parent::mergeWith($criteria, $fromScope);
if (isset($criteria['defaultValue']))
$this->defaultValue = $criteria['defaultValue'];
}
}
/**
* CActiveRelation is the base class for representing active relations that bring back related objects.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CActiveRelation extends CBaseActiveRelation
{
/**
* @var string join type. Defaults to 'LEFT OUTER JOIN'.
*/
public $joinType = 'LEFT OUTER JOIN';
/**
* @var string ON clause. The condition specified here will be appended to the joining condition using AND operator.
* @since 1.0.2
*/
public $on = '';
/**
* @var string the alias for the table that this relation refers to. Defaults to null, meaning
* the alias will be the same as the relation name.
* @since 1.0.1
*/
public $alias;
/**
* @var string|array specifies which related objects should be eagerly loaded when this related object is lazily loaded.
* For more details about this property, see {@link CActiveRecord::with()}.
*/
public $with = array();
/**
* @var boolean whether this table should be joined with the primary table.
* When setting this property to be false, the table associated with this relation will
* appear in a separate JOIN statement.
* If this property is set true, then the corresponding table will ALWAYS be joined together
* with the primary table, no matter the primary table is limited or not.
* If this property is not set, the corresponding table will be joined with the primary table
* only when the primary table is not limited.
*/
public $together;
/**
* @var mixed scopes to apply
* Can be set to the one of the following:
* <ul>
* <li>Single scope: 'scopes'=>'scopeName'.</li>
* <li>Multiple scopes: 'scopes'=>array('scopeName1','scopeName2').</li>
* </ul>
* @since 1.1.9
*/
public $scopes;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
* @since 1.0.5
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria)
$criteria = $criteria->toArray();
if ($fromScope)
{
if (isset($criteria['condition']) && $this->on !== $criteria['condition'])
{
if ($this->on === '')
$this->on = $criteria['condition'];
elseif ($criteria['condition'] !== '')
$this->on = "( {$this->on}) AND ( {$criteria['condition']})";
}
unset($criteria['condition']);
}
parent::mergeWith($criteria);
if (isset($criteria['joinType']))
$this->joinType = $criteria['joinType'];
if (isset($criteria['on']) && $this->on !== $criteria['on'])
{
if ($this->on === '')
$this->on = $criteria['on'];
elseif ($criteria['on'] !== '')
$this->on = "( {$this->on}) AND ( {$criteria['on']})";
}
if (isset($criteria['with']))
$this->with = $criteria['with'];
if (isset($criteria['alias']))
$this->alias = $criteria['alias'];
if (isset($criteria['together']))
$this->together = $criteria['together'];
}
}
/**
* CBelongsToRelation represents the parameters specifying a BELONGS_TO relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CBelongsToRelation extends CActiveRelation
{
}
/**
* CHasOneRelation represents the parameters specifying a HAS_ONE relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CHasOneRelation extends CActiveRelation
{
/**
* @var string the name of the relation that should be used as the bridge to this relation.
* Defaults to null, meaning don't use any bridge.
* @since 1.1.7
*/
public $through;
}
/**
* CHasManyRelation represents the parameters specifying a HAS_MANY relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CHasManyRelation extends CActiveRelation
{
/**
* @var integer limit of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no limit.
*/
public $limit = -1;
/**
* @var integer offset of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no offset.
*/
public $offset = -1;
/**
* @var string the name of the column that should be used as the key for storing related objects.
* Defaults to null, meaning using zero-based integer IDs.
* @since 1.0.7
*/
public $index;
/**
* @var string the name of the relation that should be used as the bridge to this relation.
* Defaults to null, meaning don't use any bridge.
* @since 1.1.7
*/
public $through;
/**
* Merges this relation with a criteria specified dynamically.
* @param array $criteria the dynamically specified criteria
* @param boolean $fromScope whether the criteria to be merged is from scopes
* @since 1.0.5
*/
public function mergeWith($criteria, $fromScope = false)
{
if ($criteria instanceof CDbCriteria)
$criteria = $criteria->toArray();
parent::mergeWith($criteria, $fromScope);
if (isset($criteria['limit']) && $criteria['limit'] > 0)
$this->limit = $criteria['limit'];
if (isset($criteria['offset']) && $criteria['offset'] >= 0)
$this->offset = $criteria['offset'];
if (isset($criteria['index']))
$this->index = $criteria['index'];
}
}
/**
* CManyManyRelation represents the parameters specifying a MANY_MANY relation.
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CManyManyRelation extends CHasManyRelation
{
}
/**
* CActiveRecordMetaData represents the meta-data for an Active Record class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecord.php 3344 2011-07-06 22:04:50Z alexander.makarow $
* @package system.db.ar
* @since 1.0
*/
class CActiveRecordMetaData
{
/**
* @var CDbTableSchema the table schema information
*/
public $tableSchema;
/**
* @var array table columns
*/
public $columns;
/**
* @var array list of relations
*/
public $relations = array();
/**
* @var array attribute default values
*/
public $attributeDefaults = array();
private $_model;
/**
* Constructor.
* @param CActiveRecord $model the model instance
*/
public function __construct($model)
{
$this->_model = $model;
$tableName = $model->tableName();
if (($table = $model->getDbConnection()->getSchema()->getTable($tableName)) === null)
throw new CDbException(Yii::t('yii', 'The table "{table}" for active record class "{class}" cannot be found in the database.',
array('{class}' => get_class($model), '{table}' => $tableName)));
if ($table->primaryKey === null)
{
$table->primaryKey = $model->primaryKey();
if (is_string($table->primaryKey) && isset($table->columns[$table->primaryKey]))
$table->columns[$table->primaryKey]->isPrimaryKey = true;
elseif (is_array($table->primaryKey))
{
foreach ($table->primaryKey as $name)
{
if (isset($table->columns[$name]))
$table->columns[$name]->isPrimaryKey = true;
}
}
}
$this->tableSchema = $table;
$this->columns = $table->columns;
foreach ($table->columns as $name => $column)
{
if (!$column->isPrimaryKey && $column->defaultValue !== null)
$this->attributeDefaults[$name] = $column->defaultValue;
}
foreach ($model->relations() as $name => $config)
{
$this->addRelation($name, $config);
}
}
/**
* Adds a relation.
*
* $config is an array with three elements:
* relation type, the related active record class and the foreign key.
*
* @throws CDbException
* @param string $name $name Name of the relation.
* @param array $config $config Relation parameters.
* @return void
* @since 1.1.2
*/
public function addRelation($name, $config)
{
if (isset($config[0], $config[1], $config[2])) // relation class, AR class, FK
$this->relations[$name] = new $config[0]($name, $config[1], $config[2], array_slice($config, 3));
else
throw new CDbException(Yii::t('yii', 'Active record "{class}" has an invalid configuration for relation "{relation}". It must specify the relation type, the related active record class and the foreign key.', array('{class}' => get_class($this->_model), '{relation}' => $name)));
}
/**
* Checks if there is a relation with specified name defined.
*
* @param string $name $name Name of the relation.
* @return boolean
* @since 1.1.2
*/
public function hasRelation($name)
{
return isset($this->relations[$name]);
}
/**
* Deletes a relation with specified name.
*
* @param string $name $name
* @return void
* @since 1.1.2
*/
public function removeRelation($name)
{
unset($this->relations[$name]);
}
}
<?php
/**
* CActiveRecordBehavior class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CActiveRecordBehavior is the base class for behaviors that can be attached to {@link CActiveRecord}.
* Compared with {@link CModelBehavior}, CActiveRecordBehavior attaches to more events
* that are only defined by {@link CActiveRecord}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CActiveRecordBehavior.php 2799 2011-01-01 19:31:13Z qiang.xue $
* @package system.db.ar
* @since 1.0.2
*/
class CActiveRecordBehavior extends CModelBehavior
{
/**
* Declares events and the corresponding event handler methods.
* If you override this method, make sure you merge the parent result to the return value.
* @return array events (array keys) and the corresponding event handler methods (array values).
* @see CBehavior::events
*/
public function events()
{
return array_merge(parent::events(), array(
'onBeforeSave' => 'beforeSave',
'onAfterSave' => 'afterSave',
'onBeforeDelete' => 'beforeDelete',
'onAfterDelete' => 'afterDelete',
'onBeforeFind' => 'beforeFind',
'onAfterFind' => 'afterFind',
));
}
/**
* Responds to {@link CActiveRecord::onBeforeSave} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* You may set {@link CModelEvent::isValid} to be false to quit the saving process.
* @param CModelEvent $event event parameter
*/
public function beforeSave($event)
{
}
/**
* Responds to {@link CActiveRecord::onAfterSave} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CModelEvent $event event parameter
*/
public function afterSave($event)
{
}
/**
* Responds to {@link CActiveRecord::onBeforeDelete} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* You may set {@link CModelEvent::isValid} to be false to quit the deletion process.
* @param CEvent $event event parameter
*/
public function beforeDelete($event)
{
}
/**
* Responds to {@link CActiveRecord::onAfterDelete} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CEvent $event event parameter
*/
public function afterDelete($event)
{
}
/**
* Responds to {@link CActiveRecord::onBeforeFind} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CEvent $event event parameter
* @since 1.0.9
*/
public function beforeFind($event)
{
}
/**
* Responds to {@link CActiveRecord::onAfterFind} event.
* Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}.
* @param CEvent $event event parameter
*/
public function afterFind($event)
{
}
}
<?php
/**
* This file contains the CDbCommand class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbCommand represents an SQL statement to execute against a database.
*
* It is usually created by calling {@link CDbConnection::createCommand}.
* The SQL statement to be executed may be set via {@link setText Text}.
*
* To execute a non-query SQL (such as insert, delete, update), call
* {@link execute}. To execute an SQL statement that returns result data set
* (such as SELECT), use {@link query} or its convenient versions {@link queryRow},
* {@link queryColumn}, or {@link queryScalar}.
*
* If an SQL statement returns results (such as a SELECT SQL), the results
* can be accessed via the returned {@link CDbDataReader}.
*
* CDbCommand supports SQL statment preparation and parameter binding.
* Call {@link bindParam} to bind a PHP variable to a parameter in SQL.
* Call {@link bindValue} to bind a value to an SQL parameter.
* When binding a parameter, the SQL statement is automatically prepared.
* You may also call {@link prepare} to explicitly prepare an SQL statement.
*
* Starting from version 1.1.6, CDbCommand can also be used as a query builder
* that builds a SQL statement from code fragments. For example,
* <pre>
* $user = Yii::app()->db->createCommand()
* ->select('username, password')
* ->from('tbl_user')
* ->where('id=:id', array(':id'=>1))
* ->queryRow();
* </pre>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbCommand.php 3240 2011-05-25 19:22:47Z qiang.xue $
* @package system.db
* @since 1.0
*/
class CDbCommand extends CComponent
{
/**
* @var array the parameters (name=>value) to be bound to the current query.
* @since 1.1.6
*/
public $params = array();
private $_connection;
private $_text;
private $_statement;
private $_paramLog = array();
private $_query;
private $_fetchMode = array(PDO::FETCH_ASSOC);
/**
* Constructor.
* @param CDbConnection $connection the database connection
* @param mixed $query the DB query to be executed. This can be either
* a string representing a SQL statement, or an array whose name-value pairs
* will be used to set the corresponding properties of the created command object.
*
* For example, you can pass in either <code>'SELECT * FROM tbl_user'</code>
* or <code>array('select'=>'*', 'from'=>'tbl_user')</code>. They are equivalent
* in terms of the final query result.
*
* When passing the query as an array, the following properties are commonly set:
* {@link select}, {@link distinct}, {@link from}, {@link where}, {@link join},
* {@link group}, {@link having}, {@link order}, {@link limit}, {@link offset} and
* {@link union}. Please refer to the setter of each of these properties for details
* about valid property values. This feature has been available since version 1.1.6.
*
* Since 1.1.7 it is possible to use a specific mode of data fetching by setting
* {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php}
* for more details.
*/
public function __construct(CDbConnection $connection, $query = null)
{
$this->_connection = $connection;
if (is_array($query))
{
foreach ($query as $name => $value)
$this->$name = $value;
}
else
$this->setText($query);
}
/**
* Set the statement to null when serializing.
* @return array
*/
public function __sleep()
{
$this->_statement = null;
return array_keys(get_object_vars($this));
}
/**
* Set the default fetch mode for this statement
* @param mixed $mode fetch mode
* @return CDbCommand
* @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
* @since 1.1.7
*/
public function setFetchMode($mode)
{
$params = func_get_args();
$this->_fetchMode = $params;
return $this;
}
/**
* Cleans up the command and prepares for building a new query.
* This method is mainly used when a command object is being reused
* multiple times for building different queries.
* Calling this method will clean up all internal states of the command object.
* @return CDbCommand this command instance
* @since 1.1.6
*/
public function reset()
{
$this->_text = null;
$this->_query = null;
$this->_statement = null;
$this->_paramLog = array();
$this->params = array();
return $this;
}
/**
* @return string the SQL statement to be executed
*/
public function getText()
{
if ($this->_text == '' && !empty($this->_query))
$this->setText($this->buildQuery($this->_query));
return $this->_text;
}
/**
* Specifies the SQL statement to be executed.
* Any previous execution will be terminated or cancel.
* @param string $value the SQL statement to be executed
* @return CDbCommand this command instance
*/
public function setText($value)
{
if ($this->_connection->tablePrefix !== null && $value != '')
$this->_text = preg_replace('/{{(.*?)}}/', $this->_connection->tablePrefix . '\1', $value);
else
$this->_text = $value;
$this->cancel();
return $this;
}
/**
* @return CDbConnection the connection associated with this command
*/
public function getConnection()
{
return $this->_connection;
}
/**
* @return PDOStatement the underlying PDOStatement for this command
* It could be null if the statement is not prepared yet.
*/
public function getPdoStatement()
{
return $this->_statement;
}
/**
* Prepares the SQL statement to be executed.
* For complex SQL statement that is to be executed multiple times,
* this may improve performance.
* For SQL statement with binding parameters, this method is invoked
* automatically.
*/
public function prepare()
{
if ($this->_statement == null)
{
try
{
$this->_statement = $this->getConnection()->getPdoInstance()->prepare($this->getText());
$this->_paramLog = array();
}
catch(Exception $e)
{
Yii::log('Error in preparing SQL: ' . $this->getText(), CLogger::LEVEL_ERROR, 'system.db.CDbCommand');
$errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
throw new CDbException(Yii::t('yii', 'CDbCommand failed to prepare the SQL statement: {error}',
array('{error}' => $e->getMessage())), (int)$e->getCode(), $errorInfo);
}
}
}
/**
* Cancels the execution of the SQL statement.
*/
public function cancel()
{
$this->_statement = null;
}
/**
* Binds a parameter to the SQL statement to be executed.
* @param mixed $name Parameter identifier. For a prepared statement
* using named placeholders, this will be a parameter name of
* the form :name. For a prepared statement using question mark
* placeholders, this will be the 1-indexed position of the parameter.
* @param mixed $value Name of the PHP variable to bind to the SQL statement parameter
* @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
* @param integer $length length of the data type
* @param mixed $driverOptions the driver-specific options (this is available since version 1.1.6)
* @return CDbCommand the current command being executed (this is available since version 1.0.8)
* @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php
*/
public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
{
$this->prepare();
if ($dataType === null)
$this->_statement->bindParam($name, $value, $this->_connection->getPdoType(gettype($value)));
elseif ($length === null)
$this->_statement->bindParam($name, $value, $dataType);
elseif ($driverOptions === null)
$this->_statement->bindParam($name, $value, $dataType, $length);
else
$this->_statement->bindParam($name, $value, $dataType, $length, $driverOptions);
$this->_paramLog[$name] =& $value;
return $this;
}
/**
* Binds a value to a parameter.
* @param mixed $name Parameter identifier. For a prepared statement
* using named placeholders, this will be a parameter name of
* the form :name. For a prepared statement using question mark
* placeholders, this will be the 1-indexed position of the parameter.
* @param mixed $value The value to bind to the parameter
* @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
* @return CDbCommand the current command being executed (this is available since version 1.0.8)
* @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
*/
public function bindValue($name, $value, $dataType = null)
{
$this->prepare();
if ($dataType === null)
$this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value)));
else
$this->_statement->bindValue($name, $value, $dataType);
$this->_paramLog[$name] = $value;
return $this;
}
/**
* Binds a list of values to the corresponding parameters.
* This is similar to {@link bindValue} except that it binds multiple values.
* Note that the SQL data type of each value is determined by its PHP type.
* @param array $values the values to be bound. This must be given in terms of an associative
* array with array keys being the parameter names, and array values the corresponding parameter values.
* For example, <code>array(':name'=>'John', ':age'=>25)</code>.
* @return CDbCommand the current command being executed
* @since 1.1.5
*/
public function bindValues($values)
{
$this->prepare();
foreach ($values as $name => $value)
{
$this->_statement->bindValue($name, $value, $this->_connection->getPdoType(gettype($value)));
$this->_paramLog[$name] = $value;
}
return $this;
}
/**
* Executes the SQL statement.
* This method is meant only for executing non-query SQL statement.
* No result set will be returned.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return integer number of rows affected by the execution.
* @throws CException execution failed
*/
public function execute($params = array())
{
if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array())
{
$p = array();
foreach ($pars as $name => $value)
$p[$name] = $name . '=' . var_export($value, true);
$par = '. Bound with ' . implode(', ', $p);
}
else
$par = '';
Yii::trace('Executing SQL: ' . $this->getText() . $par, 'system.db.CDbCommand');
try
{
if ($this->_connection->enableProfiling)
Yii::beginProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute');
$this->prepare();
if ($params === array())
$this->_statement->execute();
else
$this->_statement->execute($params);
$n = $this->_statement->rowCount();
if ($this->_connection->enableProfiling)
Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute');
return $n;
}
catch(Exception $e)
{
if ($this->_connection->enableProfiling)
Yii::endProfile('system.db.CDbCommand.execute(' . $this->getText() . ')', 'system.db.CDbCommand.execute');
$errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
$message = $e->getMessage();
Yii::log(Yii::t('yii', 'CDbCommand::execute() failed: {error}. The SQL statement executed was: {sql}.',
array('{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand');
if (YII_DEBUG)
$message .= '. The SQL statement executed was: ' . $this->getText() . $par;
throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}',
array('{error}' => $message)), (int)$e->getCode(), $errorInfo);
}
}
/**
* Executes the SQL statement and returns query result.
* This method is for executing an SQL query that returns result set.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return CDbDataReader the reader object for fetching the query result
* @throws CException execution failed
*/
public function query($params = array())
{
return $this->queryInternal('', 0, $params);
}
/**
* Executes the SQL statement and returns all rows.
* @param boolean $fetchAssociative whether each row should be returned as an associated array with
* column names as the keys or the array keys are column indexes (0-based).
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return array all rows of the query result. Each array element is an array representing a row.
* An empty array is returned if the query results in nothing.
* @throws CException execution failed
*/
public function queryAll($fetchAssociative = true, $params = array())
{
return $this->queryInternal('fetchAll', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params);
}
/**
* Executes the SQL statement and returns the first row of the result.
* This is a convenient method of {@link query} when only the first row of data is needed.
* @param boolean $fetchAssociative whether the row should be returned as an associated array with
* column names as the keys or the array keys are column indexes (0-based).
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return mixed the first row (in terms of an array) of the query result, false if no result.
* @throws CException execution failed
*/
public function queryRow($fetchAssociative = true, $params = array())
{
return $this->queryInternal('fetch', $fetchAssociative ? $this->_fetchMode : PDO::FETCH_NUM, $params);
}
/**
* Executes the SQL statement and returns the value of the first column in the first row of data.
* This is a convenient method of {@link query} when only a single scalar
* value is needed (e.g. obtaining the count of the records).
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return mixed the value of the first column in the first row of the query result. False is returned if there is no value.
* @throws CException execution failed
*/
public function queryScalar($params = array())
{
$result = $this->queryInternal('fetchColumn', 0, $params);
if (is_resource($result) && get_resource_type($result) === 'stream')
return stream_get_contents($result);
else
return $result;
}
/**
* Executes the SQL statement and returns the first column of the result.
* This is a convenient method of {@link query} when only the first column of data is needed.
* Note, the column returned will contain the first element in each row of result.
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that if you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return array the first column of the query result. Empty array if no result.
* @throws CException execution failed
*/
public function queryColumn($params = array())
{
return $this->queryInternal('fetchAll', PDO::FETCH_COLUMN, $params);
}
/**
* @param string $method method of PDOStatement to be called
* @param mixed $mode parameters to be passed to the method
* @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
* to {@link bindParam} and {@link bindValue}. If you have multiple input parameters, passing
* them in this way can improve the performance. Note that you pass parameters in this way,
* you cannot bind parameters or values using {@link bindParam} or {@link bindValue}, and vice versa.
* binding methods and the input parameters this way can improve the performance.
* This parameter has been available since version 1.0.10.
* @return mixed the method execution result
*/
private function queryInternal($method, $mode, $params = array())
{
$params = array_merge($this->params, $params);
if ($this->_connection->enableParamLogging && ($pars = array_merge($this->_paramLog, $params)) !== array())
{
$p = array();
foreach ($pars as $name => $value)
$p[$name] = $name . '=' . var_export($value, true);
$par = '. Bound with ' . implode(', ', $p);
}
else
$par = '';
Yii::trace('Querying SQL: ' . $this->getText() . $par, 'system.db.CDbCommand');
if ($this->_connection->queryCachingCount > 0 && $method !== ''
&& $this->_connection->queryCachingDuration > 0
&& $this->_connection->queryCacheID !== false
&& ($cache = Yii::app()->getComponent($this->_connection->queryCacheID)) !== null)
{
$this->_connection->queryCachingCount--;
$cacheKey = 'yii:dbquery' . $this->_connection->connectionString . ':' . $this->_connection->username;
$cacheKey .= ':' . $this->getText() . ':' . serialize(array_merge($this->_paramLog, $params));
if (($result = $cache->get($cacheKey)) !== false)
{
Yii::trace('Query result found in cache', 'system.db.CDbCommand');
return $result;
}
}
try
{
if ($this->_connection->enableProfiling)
Yii::beginProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query');
$this->prepare();
if ($params === array())
$this->_statement->execute();
else
$this->_statement->execute($params);
if ($method === '')
$result = new CDbDataReader($this);
else
{
$mode = (array)$mode;
$result = call_user_func_array(array($this->_statement, $method), $mode);
$this->_statement->closeCursor();
}
if ($this->_connection->enableProfiling)
Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query');
if (isset($cache, $cacheKey))
$cache->set($cacheKey, $result, $this->_connection->queryCachingDuration, $this->_connection->queryCachingDependency);
return $result;
}
catch(Exception $e)
{
if ($this->_connection->enableProfiling)
Yii::endProfile('system.db.CDbCommand.query(' . $this->getText() . $par . ')', 'system.db.CDbCommand.query');
$errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
$message = $e->getMessage();
Yii::log(Yii::t('yii', 'CDbCommand::{method}() failed: {error}. The SQL statement executed was: {sql}.',
array('{method}' => $method, '{error}' => $message, '{sql}' => $this->getText() . $par)), CLogger::LEVEL_ERROR, 'system.db.CDbCommand');
if (YII_DEBUG)
$message .= '. The SQL statement executed was: ' . $this->getText() . $par;
throw new CDbException(Yii::t('yii', 'CDbCommand failed to execute the SQL statement: {error}',
array('{error}' => $message)), (int)$e->getCode(), $errorInfo);
}
}
/**
* Builds a SQL SELECT statement from the given query specification.
* @param array $query the query specification in name-value pairs. The following
* query options are supported: {@link select}, {@link distinct}, {@link from},
* {@link where}, {@link join}, {@link group}, {@link having}, {@link order},
* {@link limit}, {@link offset} and {@link union}.
* @return string the SQL statement
* @since 1.1.6
*/
public function buildQuery($query)
{
$sql = isset($query['distinct']) && $query['distinct'] ? 'SELECT DISTINCT' : 'SELECT';
$sql .= ' ' . (isset($query['select']) ? $query['select'] : '*');
if (isset($query['from']))
$sql .= "\nFROM " . $query['from'];
else
throw new CDbException(Yii::t('yii', 'The DB query must contain the "from" portion.'));
if (isset($query['join']))
$sql .= "\n" . (is_array($query['join']) ? implode("\n", $query['join']) : $query['join']);
if (isset($query['where']))
$sql .= "\nWHERE " . $query['where'];
if (isset($query['group']))
$sql .= "\nGROUP BY " . $query['group'];
if (isset($query['having']))
$sql .= "\nHAVING " . $query['having'];
if (isset($query['order']))
$sql .= "\nORDER BY " . $query['order'];
$limit = isset($query['limit']) ? (int)$query['limit'] : -1;
$offset = isset($query['offset']) ? (int)$query['offset'] : -1;
if ($limit >= 0 || $offset > 0)
$sql = $this->_connection->getCommandBuilder()->applyLimit($sql, $limit, $offset);
if (isset($query['union']))
$sql .= "\nUNION (\n" . (is_array($query['union']) ? implode("\n) UNION (\n", $query['union']) : $query['union']) . ')';
return $sql;
}
/**
* Sets the SELECT part of the query.
* @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. This parameter is supported since version 1.1.8.
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function select($columns = '*', $option = '')
{
if (is_string($columns) && strpos($columns, '(') !== false)
$this->_query['select'] = $columns;
else
{
if (!is_array($columns))
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $column)
{
if (is_object($column))
$columns[$i] = (string)$column;
elseif (strpos($column, '(') === false)
{
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $column, $matches))
$columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' AS ' . $this->_connection->quoteColumnName($matches[2]);
else
$columns[$i] = $this->_connection->quoteColumnName($column);
}
}
$this->_query['select'] = implode(', ', $columns);
}
if ($option != '')
$this->_query['select'] = $option . ' ' . $this->_query['select'];
return $this;
}
/**
* Returns the SELECT part in the query.
* @return string the SELECT part (without 'SELECT') in the query.
* @since 1.1.6
*/
public function getSelect()
{
return isset($this->_query['select']) ? $this->_query['select'] : '';
}
/**
* Sets the SELECT part in the query.
* @param mixed $value the data to be selected. Please refer to {@link select()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setSelect($value)
{
$this->select($value);
}
/**
* Sets the SELECT part of the query with the DISTINCT flag turned on.
* This is the same as {@link select} except that the DISTINCT flag is turned on.
* @param mixed $columns the columns to be selected. See {@link select} for more details.
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function selectDistinct($columns = '*')
{
$this->_query['distinct'] = true;
return $this->select($columns);
}
/**
* Returns a value indicating whether SELECT DISTINCT should be used.
* @return boolean a value indicating whether SELECT DISTINCT should be used.
* @since 1.1.6
*/
public function getDistinct()
{
return isset($this->_query['distinct']) ? $this->_query['distinct'] : false;
}
/**
* Sets a value indicating whether SELECT DISTINCT should be used.
* @param boolean $value a value indicating whether SELECT DISTINCT should be used.
* @since 1.1.6
*/
public function setDistinct($value)
{
$this->_query['distinct'] = $value;
}
/**
* Sets the FROM part of the query.
* @param mixed $tables the table(s) to be selected from. This can be either a string (e.g. 'tbl_user')
* or an array (e.g. array('tbl_user', 'tbl_profile')) specifying one or several table names.
* Table names can contain schema prefixes (e.g. 'public.tbl_user') and/or table aliases (e.g. 'tbl_user u').
* The method will automatically quote the table names unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function from($tables)
{
if (is_string($tables) && strpos($tables, '(') !== false)
$this->_query['from'] = $tables;
else
{
if (!is_array($tables))
$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
foreach ($tables as $i => $table)
{
if (strpos($table, '(') === false)
{
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias
$tables[$i] = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]);
else
$tables[$i] = $this->_connection->quoteTableName($table);
}
}
$this->_query['from'] = implode(', ', $tables);
}
return $this;
}
/**
* Returns the FROM part in the query.
* @return string the FROM part (without 'FROM' ) in the query.
* @since 1.1.6
*/
public function getFrom()
{
return isset($this->_query['from']) ? $this->_query['from'] : '';
}
/**
* Sets the FROM part in the query.
* @param mixed $value the tables to be selected from. Please refer to {@link from()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setFrom($value)
{
$this->from($value);
}
/**
* Sets the WHERE part of the query.
*
* The method requires a $conditions parameter, and optionally a $params parameter
* specifying the values to be bound to the query.
*
* The $conditions parameter should be either a string (e.g. 'id=1') or an array.
* If the latter, it must be of the format <code>array(operator, operand1, operand2, ...)</code>,
* where the operator can be one of the followings, and the possible operands depend on the corresponding
* operator:
* <ul>
* <li><code>and</code>: the operands should be concatenated together using AND. For example,
* array('and', 'id=1', 'id=2') will generate 'id=1 AND id=2'. If an operand is an array,
* it will be converted into a string using the same rules described here. For example,
* array('and', 'type=1', array('or', 'id=1', 'id=2')) will generate 'type=1 AND (id=1 OR id=2)'.
* The method will NOT do any quoting or escaping.</li>
* <li><code>or</code>: similar as the <code>and</code> operator except that the operands are concatenated using OR.</li>
* <li><code>in</code>: operand 1 should be a column or DB expression, and operand 2 be an array representing
* the range of the values that the column or DB expression should be in. For example,
* array('in', 'id', array(1,2,3)) will generate 'id IN (1,2,3)'.
* The method will properly quote the column name and escape values in the range.</li>
* <li><code>not in</code>: similar as the <code>in</code> operator except that IN is replaced with NOT IN in the generated condition.</li>
* <li><code>like</code>: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
* the values that the column or DB expression should be like.
* For example, array('like', 'name', '%tester%') will generate "name LIKE '%tester%'".
* When the value range is given as an array, multiple LIKE predicates will be generated and concatenated using AND.
* For example, array('like', 'name', array('%test%', '%sample%')) will generate
* "name LIKE '%test%' AND name LIKE '%sample%'".
* The method will properly quote the column name and escape values in the range.</li>
* <li><code>not like</code>: similar as the <code>like</code> operator except that LIKE is replaced with NOT LIKE in the generated condition.</li>
* <li><code>or like</code>: similar as the <code>like</code> operator except that OR is used to concatenated the LIKE predicates.</li>
* <li><code>or not like</code>: similar as the <code>not like</code> operator except that OR is used to concatenated the NOT LIKE predicates.</li>
* </ul>
* @param mixed $conditions the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function where($conditions, $params = array())
{
$this->_query['where'] = $this->processConditions($conditions);
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
/**
* Returns the WHERE part in the query.
* @return string the WHERE part (without 'WHERE' ) in the query.
* @since 1.1.6
*/
public function getWhere()
{
return isset($this->_query['where']) ? $this->_query['where'] : '';
}
/**
* Sets the WHERE part in the query.
* @param mixed $value the where part. Please refer to {@link where()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setWhere($value)
{
$this->where($value);
}
/**
* Appends an INNER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function join($table, $conditions, $params = array())
{
return $this->joinInternal('join', $table, $conditions, $params);
}
/**
* Returns the join part in the query.
* @return mixed the join part in the query. This can be an array representing
* multiple join fragments, or a string representing a single jojin fragment.
* Each join fragment will contain the proper join operator (e.g. LEFT JOIN).
* @since 1.1.6
*/
public function getJoin()
{
return isset($this->_query['join']) ? $this->_query['join'] : '';
}
/**
* Sets the join part in the query.
* @param mixed $value the join part in the query. This can be either a string or
* an array representing multiple join parts in the query. Each part must contain
* the proper join operator (e.g. 'LEFT JOIN tbl_profile ON tbl_user.id=tbl_profile.id')
* @since 1.1.6
*/
public function setJoin($value)
{
$this->_query['join'] = $value;
}
/**
* Appends a LEFT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function leftJoin($table, $conditions, $params = array())
{
return $this->joinInternal('left join', $table, $conditions, $params);
}
/**
* Appends a RIGHT OUTER JOIN part to the query.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function rightJoin($table, $conditions, $params = array())
{
return $this->joinInternal('right join', $table, $conditions, $params);
}
/**
* Appends a CROSS JOIN part to the query.
* Note that not all DBMS support CROSS JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function crossJoin($table)
{
return $this->joinInternal('cross join', $table);
}
/**
* Appends a NATURAL JOIN part to the query.
* Note that not all DBMS support NATURAL JOIN.
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function naturalJoin($table)
{
return $this->joinInternal('natural join', $table);
}
/**
* Sets the GROUP BY part of the query.
* @param mixed $columns the columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function group($columns)
{
if (is_string($columns) && strpos($columns, '(') !== false)
$this->_query['group'] = $columns;
else
{
if (!is_array($columns))
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $column)
{
if (is_object($column))
$columns[$i] = (string)$column;
elseif (strpos($column, '(') === false)
$columns[$i] = $this->_connection->quoteColumnName($column);
}
$this->_query['group'] = implode(', ', $columns);
}
return $this;
}
/**
* Returns the GROUP BY part in the query.
* @return string the GROUP BY part (without 'GROUP BY' ) in the query.
* @since 1.1.6
*/
public function getGroup()
{
return isset($this->_query['group']) ? $this->_query['group'] : '';
}
/**
* Sets the GROUP BY part in the query.
* @param mixed $value the GROUP BY part. Please refer to {@link group()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setGroup($value)
{
$this->group($value);
}
/**
* Sets the HAVING part of the query.
* @param mixed $conditions the conditions to be put after HAVING.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function having($conditions, $params = array())
{
$this->_query['having'] = $this->processConditions($conditions);
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
/**
* Returns the HAVING part in the query.
* @return string the HAVING part (without 'HAVING' ) in the query.
* @since 1.1.6
*/
public function getHaving()
{
return isset($this->_query['having']) ? $this->_query['having'] : '';
}
/**
* Sets the HAVING part in the query.
* @param mixed $value the HAVING part. Please refer to {@link having()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setHaving($value)
{
$this->having($value);
}
/**
* Sets the ORDER BY part of the query.
* @param mixed $columns the columns (and the directions) to be ordered by.
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function order($columns)
{
if (is_string($columns) && strpos($columns, '(') !== false)
$this->_query['order'] = $columns;
else
{
if (!is_array($columns))
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
foreach ($columns as $i => $column)
{
if (is_object($column))
$columns[$i] = (string)$column;
elseif (strpos($column, '(') === false)
{
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches))
$columns[$i] = $this->_connection->quoteColumnName($matches[1]) . ' ' . strtoupper($matches[2]);
else
$columns[$i] = $this->_connection->quoteColumnName($column);
}
}
$this->_query['order'] = implode(', ', $columns);
}
return $this;
}
/**
* Returns the ORDER BY part in the query.
* @return string the ORDER BY part (without 'ORDER BY' ) in the query.
* @since 1.1.6
*/
public function getOrder()
{
return isset($this->_query['order']) ? $this->_query['order'] : '';
}
/**
* Sets the ORDER BY part in the query.
* @param mixed $value the ORDER BY part. Please refer to {@link order()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setOrder($value)
{
$this->order($value);
}
/**
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @param integer $offset the offset
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function limit($limit, $offset = null)
{
$this->_query['limit'] = (int)$limit;
if ($offset !== null)
$this->offset($offset);
return $this;
}
/**
* Returns the LIMIT part in the query.
* @return string the LIMIT part (without 'LIMIT' ) in the query.
* @since 1.1.6
*/
public function getLimit()
{
return isset($this->_query['limit']) ? $this->_query['limit'] : -1;
}
/**
* Sets the LIMIT part in the query.
* @param integer $value the LIMIT part. Please refer to {@link limit()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setLimit($value)
{
$this->limit($value);
}
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function offset($offset)
{
$this->_query['offset'] = (int)$offset;
return $this;
}
/**
* Returns the OFFSET part in the query.
* @return string the OFFSET part (without 'OFFSET' ) in the query.
* @since 1.1.6
*/
public function getOffset()
{
return isset($this->_query['offset']) ? $this->_query['offset'] : -1;
}
/**
* Sets the OFFSET part in the query.
* @param integer $value the OFFSET part. Please refer to {@link offset()} for details
* on how to specify this parameter.
* @since 1.1.6
*/
public function setOffset($value)
{
$this->offset($value);
}
/**
* Appends a SQL statement using UNION operator.
* @param string $sql the SQL statement to be appended using UNION
* @return CDbCommand the command object itself
* @since 1.1.6
*/
public function union($sql)
{
if (isset($this->_query['union']) && is_string($this->_query['union']))
$this->_query['union'] = array($this->_query['union']);
$this->_query['union'][] = $sql;
return $this;
}
/**
* Returns the UNION part in the query.
* @return mixed the UNION part (without 'UNION' ) in the query.
* This can be either a string or an array representing multiple union parts.
* @since 1.1.6
*/
public function getUnion()
{
return isset($this->_query['union']) ? $this->_query['union'] : '';
}
/**
* Sets the UNION part in the query.
* @param mixed $value the UNION part. This can be either a string or an array
* representing multiple SQL statements to be unioned together.
* @since 1.1.6
*/
public function setUnion($value)
{
$this->_query['union'] = $value;
}
/**
* Creates and executes an INSERT SQL statement.
* The method will properly escape the column names, and bind the values to be inserted.
* @param string $table the table that new rows will be inserted into.
* @param array $columns the column data (name=>value) to be inserted into the table.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function insert($table, $columns)
{
$params = array();
$names = array();
$placeholders = array();
foreach ($columns as $name => $value)
{
$names[] = $this->_connection->quoteColumnName($name);
if ($value instanceof CDbExpression)
{
$placeholders[] = $value->expression;
foreach ($value->params as $n => $v)
$params[$n] = $v;
}
else
{
$placeholders[] = ':' . $name;
$params[':' . $name] = $value;
}
}
$sql = 'INSERT INTO ' . $this->_connection->quoteTableName($table)
. ' (' . implode(', ', $names) . ') VALUES ('
. implode(', ', $placeholders) . ')';
return $this->setText($sql)->execute($params);
}
/**
* Creates and executes an UPDATE SQL statement.
* The method will properly escape the column names and bind the values to be updated.
* @param string $table the table to be updated.
* @param array $columns the column data (name=>value) to be updated.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please
* refer to {@link where} on how to specify conditions.
* @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function update($table, $columns, $conditions = '', $params = array())
{
$lines = array();
foreach ($columns as $name => $value)
{
if ($value instanceof CDbExpression)
{
$lines[] = $this->_connection->quoteColumnName($name) . '=' . $value->expression;
foreach ($value->params as $n => $v)
$params[$n] = $v;
}
else
{
$lines[] = $this->_connection->quoteColumnName($name) . '=:' . $name;
$params[':' . $name] = $value;
}
}
$sql = 'UPDATE ' . $this->_connection->quoteTableName($table) . ' SET ' . implode(', ', $lines);
if (($where = $this->processConditions($conditions)) != '')
$sql .= ' WHERE ' . $where;
return $this->setText($sql)->execute($params);
}
/**
* Creates and executes a DELETE SQL statement.
* @param string $table the table where the data will be deleted from.
* @param mixed $conditions the conditions that will be put in the WHERE part. Please
* refer to {@link where} on how to specify conditions.
* @param array $params the parameters to be bound to the query.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function delete($table, $conditions = '', $params = array())
{
$sql = 'DELETE FROM ' . $this->_connection->quoteTableName($table);
if (($where = $this->processConditions($conditions)) != '')
$sql .= ' WHERE ' . $where;
return $this->setText($sql)->execute($params);
}
/**
* Builds and executes a SQL statement for creating a new DB table.
*
* The columns in the new table should be specified as name-definition pairs (e.g. 'name'=>'string'),
* where name stands for a column name which will be properly quoted by the method, and definition
* stands for the column type which can contain an abstract DB type.
* The {@link getColumnType} method will be invoked to convert any abstract type into a physical one.
*
* If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
* inserted into the generated SQL.
*
* @param string $table the name of the table to be created. The name will be properly quoted by the method.
* @param array $columns the columns (name=>definition) in the new table.
* @param string $options additional SQL fragment that will be appended to the generated SQL.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function createTable($table, $columns, $options = null)
{
return $this->setText($this->getConnection()->getSchema()->createTable($table, $columns, $options))->execute();
}
/**
* Builds and executes a SQL statement for renaming a DB table.
* @param string $table the table to be renamed. The name will be properly quoted by the method.
* @param string $newName the new table name. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function renameTable($table, $newName)
{
return $this->setText($this->getConnection()->getSchema()->renameTable($table, $newName))->execute();
}
/**
* Builds and executes a SQL statement for dropping a DB table.
* @param string $table the table to be dropped. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function dropTable($table)
{
return $this->setText($this->getConnection()->getSchema()->dropTable($table))->execute();
}
/**
* Builds and executes a SQL statement for truncating a DB table.
* @param string $table the table to be truncated. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function truncateTable($table)
{
$schema = $this->getConnection()->getSchema();
$n = $this->setText($schema->truncateTable($table))->execute();
if (strncasecmp($this->getConnection()->getDriverName(), 'sqlite', 6) === 0)
$schema->resetSequence($schema->getTable($table));
return $n;
}
/**
* Builds and executes a SQL statement for adding a new DB column.
* @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
* @param string $column the name of the new column. The name will be properly quoted by the method.
* @param string $type the column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function addColumn($table, $column, $type)
{
return $this->setText($this->getConnection()->getSchema()->addColumn($table, $column, $type))->execute();
}
/**
* Builds and executes a SQL statement for dropping a DB column.
* @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
* @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function dropColumn($table, $column)
{
return $this->setText($this->getConnection()->getSchema()->dropColumn($table, $column))->execute();
}
/**
* Builds and executes a SQL statement for renaming a column.
* @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
* @param string $name the old name of the column. The name will be properly quoted by the method.
* @param string $newName the new name of the column. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function renameColumn($table, $name, $newName)
{
return $this->setText($this->getConnection()->getSchema()->renameColumn($table, $name, $newName))->execute();
}
/**
* Builds and executes a SQL statement for changing the definition of a column.
* @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
* @param string $column the name of the column to be changed. The name will be properly quoted by the method.
* @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
* into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
* For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function alterColumn($table, $column, $type)
{
return $this->setText($this->getConnection()->getSchema()->alterColumn($table, $column, $type))->execute();
}
/**
* Builds a SQL statement for adding a foreign key constraint to an existing table.
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
* @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
* @param string $refTable the table that the foreign key references to.
* @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
{
return $this->setText($this->getConnection()->getSchema()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update))->execute();
}
/**
* Builds a SQL statement for dropping a foreign key constraint.
* @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function dropForeignKey($name, $table)
{
return $this->setText($this->getConnection()->getSchema()->dropForeignKey($name, $table))->execute();
}
/**
* Builds and executes a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
* @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
* by commas. The column names will be properly quoted by the method.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function createIndex($name, $table, $column, $unique = false)
{
return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute();
}
/**
* Builds and executes a SQL statement for dropping an index.
* @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
* @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
public function dropIndex($name, $table)
{
return $this->setText($this->getConnection()->getSchema()->dropIndex($name, $table))->execute();
}
/**
* Generates the condition string that will be put in the WHERE part
* @param mixed $conditions the conditions that will be put in the WHERE part.
* @return string the condition string to put in the WHERE part
*/
private function processConditions($conditions)
{
if (!is_array($conditions))
return $conditions;
elseif ($conditions === array())
return '';
$n = count($conditions);
$operator = strtoupper($conditions[0]);
if ($operator === 'OR' || $operator === 'AND')
{
$parts = array();
for ($i = 1;$i < $n;++$i)
{
$condition = $this->processConditions($conditions[$i]);
if ($condition !== '')
$parts[] = '(' . $condition . ')';
}
return $parts === array() ? '' : implode(' ' . $operator . ' ', $parts);
}
if (!isset($conditions[1], $conditions[2]))
return '';
$column = $conditions[1];
if (strpos($column, '(') === false)
$column = $this->_connection->quoteColumnName($column);
$values = $conditions[2];
if (!is_array($values))
$values = array($values);
if ($operator === 'IN' || $operator === 'NOT IN')
{
if ($values === array())
return $operator === 'IN' ? '0=1' : '';
foreach ($values as $i => $value)
{
if (is_string($value))
$values[$i] = $this->_connection->quoteValue($value);
else
$values[$i] = (string)$value;
}
return $column . ' ' . $operator . ' (' . implode(', ', $values) . ')';
}
if ($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE')
{
if ($values === array())
return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
if ($operator === 'LIKE' || $operator === 'NOT LIKE')
$andor = ' AND ';
else
{
$andor = ' OR ';
$operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
}
$expressions = array();
foreach ($values as $value)
$expressions[] = $column . ' ' . $operator . ' ' . $this->_connection->quoteValue($value);
return implode($andor, $expressions);
}
throw new CDbException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}' => $operator)));
}
/**
* Appends an JOIN part to the query.
* @param string $type the join type ('join', 'left join', 'right join', 'cross join', 'natural join')
* @param string $table the table to be joined.
* Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
* The method will automatically quote the table name unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
* @return CDbCommand the command object itself
* @since 1.1.6
*/
private function joinInternal($type, $table, $conditions = '', $params = array())
{
if (strpos($table, '(') === false)
{
if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) // with alias
$table = $this->_connection->quoteTableName($matches[1]) . ' ' . $this->_connection->quoteTableName($matches[2]);
else
$table = $this->_connection->quoteTableName($table);
}
$conditions = $this->processConditions($conditions);
if ($conditions != '')
$conditions = ' ON ' . $conditions;
if (isset($this->_query['join']) && is_string($this->_query['join']))
$this->_query['join'] = array($this->_query['join']);
$this->_query['join'][] = strtoupper($type) . ' ' . $table . $conditions;
foreach ($params as $name => $value)
$this->params[$name] = $value;
return $this;
}
}
<?php
/**
* Connection class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* Connection represents a connection to a database.
*
* Connection works together with {@link CDbCommand}, {@link CDbDataReader}
* and {@link CDbTransaction} to provide data access to various DBMS
* in a common set of APIs. They are a thin wrapper of the {@link http://www.php.net/manual/en/ref.pdo.php PDO}
* PHP extension.
*
* To establish a connection, set {@link setActive active} to true after
* specifying {@link connectionString}, {@link username} and {@link password}.
*
* The following example shows how to create a Connection instance and establish
* the actual connection:
* <pre>
* $connection=new Connection($dsn,$username,$password);
* $connection->active=true;
* </pre>
*
* After the DB connection is established, one can execute an SQL statement like the following:
* <pre>
* $command=$connection->createCommand($sqlStatement);
* $command->execute(); // a non-query SQL statement execution
* // or execute an SQL query and fetch the result set
* $reader=$command->query();
*
* // each $row is an array representing a row of data
* foreach($reader as $row) ...
* </pre>
*
* One can do prepared SQL execution and bind parameters to the prepared SQL:
* <pre>
* $command=$connection->createCommand($sqlStatement);
* $command->bindParam($name1,$value1);
* $command->bindParam($name2,$value2);
* $command->execute();
* </pre>
*
* To use transaction, do like the following:
* <pre>
* $transaction=$connection->beginTransaction();
* try
* {
* $connection->createCommand($sql1)->execute();
* $connection->createCommand($sql2)->execute();
* //.... other SQL executions
* $transaction->commit();
* }
* catch(Exception $e)
* {
* $transaction->rollBack();
* }
* </pre>
*
* Connection also provides a set of methods to support setting and querying
* of certain DBMS attributes, such as {@link getNullConversion nullConversion}.
*
* Since Connection implements the interface IApplicationComponent, it can
* be used as an application component and be configured in application configuration,
* like the following,
* <pre>
* array(
* 'components'=>array(
* 'db'=>array(
* 'class'=>'Connection',
* 'connectionString'=>'sqlite:path/to/dbfile',
* ),
* ),
* )
* </pre>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Connection extends \yii\base\ApplicationComponent
{
/**
* @var string The Data Source Name, or DSN, contains the information required to connect to the database.
* @see http://www.php.net/manual/en/function.PDO-construct.php
*
* Note that if your database is using GBK or BIG5 charset, we highly recommend you
* to upgrade to PHP 5.3.6+ and specify charset via DSN like the following to prevent
* from hacking: `mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;`.
*/
public $dsn;
/**
* @var string the username for establishing DB connection. Defaults to empty string.
*/
public $username = '';
/**
* @var string the password for establishing DB connection. Defaults to empty string.
*/
public $password = '';
/**
* @var integer number of seconds that table metadata can remain valid in cache.
* Use 0 or negative value to indicate not caching schema.
* If greater than 0 and the primary cache is enabled, the table metadata will be cached.
* @see schemaCachingExclude
*/
public $schemaCachingDuration = 0;
/**
* @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
* @see schemaCachingDuration
*/
public $schemaCachingExclude = array();
/**
* @var string the ID of the cache application component that is used to cache the table metadata.
* Defaults to 'cache' which refers to the primary cache application component.
* Set this property to false if you want to disable caching table metadata.
* @since 1.0.10
*/
public $schemaCacheID = 'cache';
/**
* @var integer number of seconds that query results can remain valid in cache.
* Use 0 or negative value to indicate not caching query results (the default behavior).
*
* In order to enable query caching, this property must be a positive
* integer and {@link queryCacheID} must point to a valid cache component ID.
*
* The method {@link cache()} is provided as a convenient way of setting this property
* and {@link queryCachingDependency} on the fly.
*
* @see cache
* @see queryCachingDependency
* @see queryCacheID
* @since 1.1.7
*/
public $queryCachingDuration = 0;
/**
* @var CCacheDependency the dependency that will be used when saving query results into cache.
* @see queryCachingDuration
* @since 1.1.7
*/
public $queryCachingDependency;
/**
* @var integer the number of SQL statements that need to be cached next.
* If this is 0, then even if query caching is enabled, no query will be cached.
* Note that each time after executing a SQL statement (whether executed on DB server or fetched from
* query cache), this property will be reduced by 1 until 0.
* @since 1.1.7
*/
public $queryCachingCount = 0;
/**
* @var string the ID of the cache application component that is used for query caching.
* Defaults to 'cache' which refers to the primary cache application component.
* Set this property to false if you want to disable query caching.
* @since 1.1.7
*/
public $queryCacheID = 'cache';
/**
* @var boolean whether the database connection should be automatically established
* the component is being initialized. Defaults to true. Note, this property is only
* effective when the Connection object is used as an application component.
*/
public $autoConnect = true;
/**
* @var string the charset used for database connection. The property is only used
* for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset
* as specified by the database.
*
* Note that if you're using GBK or BIG5 then it's highly recommended to
* update to PHP 5.3.6+ and to specify charset via DSN like
* 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'.
*/
public $charset;
/**
* @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO
* will use the native prepare support if available. For some databases (such as MySQL),
* this may need to be set true so that PDO can emulate the prepare support to bypass
* the buggy native prepare support. Note, this property is only effective for PHP 5.1.3 or above.
* The default value is null, which will not change the ATTR_EMULATE_PREPARES value of PDO.
*/
public $emulatePrepare;
/**
* @var boolean whether to log the values that are bound to a prepare SQL statement.
* Defaults to false. During development, you may consider setting this property to true
* so that parameter values bound to SQL statements are logged for debugging purpose.
* You should be aware that logging parameter values could be expensive and have significant
* impact on the performance of your application.
* @since 1.0.5
*/
public $enableParamLogging = false;
/**
* @var boolean whether to enable profiling the SQL statements being executed.
* Defaults to false. This should be mainly enabled and used during development
* to find out the bottleneck of SQL executions.
* @since 1.0.6
*/
public $enableProfiling = false;
/**
* @var string the default prefix for table names. Defaults to null, meaning no table prefix.
* By setting this property, any token like '{{tableName}}' in {@link CDbCommand::text} will
* be replaced by 'prefixTableName', where 'prefix' refers to this property value.
* @since 1.1.0
*/
public $tablePrefix;
/**
* @var array list of SQL statements that should be executed right after the DB connection is established.
* @since 1.1.1
*/
public $initSQLs;
/**
* @var array mapping between PDO driver and schema class name.
* A schema class can be specified using path alias.
* @since 1.1.6
*/
public $driverMap = array(
'pgsql' => 'CPgsqlSchema', // PostgreSQL
'mysqli' => 'CMysqlSchema', // MySQL
'mysql' => 'CMysqlSchema', // MySQL
'sqlite' => 'CSqliteSchema', // sqlite 3
'sqlite2' => 'CSqliteSchema', // sqlite 2
'mssql' => 'CMssqlSchema', // Mssql driver on windows hosts
'dblib' => 'CMssqlSchema', // dblib drivers on linux (and maybe others os) hosts
'sqlsrv' => 'CMssqlSchema', // Mssql
'oci' => 'COciSchema', // Oracle driver
);
/**
* @var string Custom PDO wrapper class.
* @since 1.1.8
*/
public $pdoClass = 'PDO';
private $_attributes = array();
private $_active = false;
private $_pdo;
private $_transaction;
private $_schema;
/**
* Constructor.
* Note, the DB connection is not established when this connection
* instance is created. Set {@link setActive active} property to true
* to establish the connection.
* @param string $dsn The Data Source Name, or DSN, contains the information required to connect to the database.
* @param string $username The user name for the DSN string.
* @param string $password The password for the DSN string.
* @see http://www.php.net/manual/en/function.PDO-construct.php
*/
public function __construct($dsn = '', $username = '', $password = '')
{
$this->connectionString = $dsn;
$this->username = $username;
$this->password = $password;
}
/**
* Close the connection when serializing.
* @return array
*/
public function __sleep()
{
$this->close();
return array_keys(get_object_vars($this));
}
/**
* Returns a list of available PDO drivers.
* @return array list of available PDO drivers
* @see http://www.php.net/manual/en/function.PDO-getAvailableDrivers.php
*/
public static function getAvailableDrivers()
{
return PDO::getAvailableDrivers();
}
/**
* Initializes the component.
* This method is required by {@link IApplicationComponent} and is invoked by application
* when the Connection is used as an application component.
* If you override this method, make sure to call the parent implementation
* so that the component can be marked as initialized.
*/
public function init()
{
parent::init();
if ($this->autoConnect)
$this->setActive(true);
}
/**
* Returns whether the DB connection is established.
* @return boolean whether the DB connection is established
*/
public function getActive()
{
return $this->_active;
}
/**
* Open or close the DB connection.
* @param boolean $value whether to open or close DB connection
* @throws CException if connection fails
*/
public function setActive($value)
{
if ($value != $this->_active)
{
if ($value)
$this->open();
else
$this->close();
}
}
/**
* Sets the parameters about query caching.
* This method can be used to enable or disable query caching.
* By setting the $duration parameter to be 0, the query caching will be disabled.
* Otherwise, query results of the new SQL statements executed next will be saved in cache
* and remain valid for the specified duration.
* If the same query is executed again, the result may be fetched from cache directly
* without actually executing the SQL statement.
* @param integer $duration the number of seconds that query results may remain valid in cache.
* If this is 0, the caching will be disabled.
* @param CCacheDependency $dependency the dependency that will be used when saving the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
* @return Connection the connection instance itself.
* @since 1.1.7
*/
public function cache($duration, $dependency = null, $queryCount = 1)
{
$this->queryCachingDuration = $duration;
$this->queryCachingDependency = $dependency;
$this->queryCachingCount = $queryCount;
return $this;
}
/**
* Opens DB connection if it is currently not
* @throws CException if connection fails
*/
protected function open()
{
if ($this->_pdo === null)
{
if (empty($this->connectionString))
throw new CDbException(Yii::t('yii', 'Connection.connectionString cannot be empty.'));
try
{
Yii::trace('Opening DB connection', 'system.db.Connection');
$this->_pdo = $this->createPdoInstance();
$this->initConnection($this->_pdo);
$this->_active = true;
}
catch(PDOException $e)
{
if (YII_DEBUG)
{
throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection: {error}',
array('{error}' => $e->getMessage())), (int)$e->getCode(), $e->errorInfo);
}
else
{
Yii::log($e->getMessage(), CLogger::LEVEL_ERROR, 'exception.CDbException');
throw new CDbException(Yii::t('yii', 'Connection failed to open the DB connection.'), (int)$e->getCode(), $e->errorInfo);
}
}
}
}
/**
* Closes the currently active DB connection.
* It does nothing if the connection is already closed.
*/
protected function close()
{
Yii::trace('Closing DB connection', 'system.db.Connection');
$this->_pdo = null;
$this->_active = false;
$this->_schema = null;
}
/**
* Creates the PDO instance.
* When some functionalities are missing in the pdo driver, we may use
* an adapter class to provides them.
* @return PDO the pdo instance
* @since 1.0.4
*/
protected function createPdoInstance()
{
$pdoClass = $this->pdoClass;
if (($pos = strpos($this->connectionString, ':')) !== false)
{
$driver = strtolower(substr($this->connectionString, 0, $pos));
if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv')
$pdoClass = 'CMssqlPdoAdapter';
}
return new $pdoClass($this->connectionString, $this->username,
$this->password, $this->_attributes);
}
/**
* Initializes the open db connection.
* This method is invoked right after the db connection is established.
* The default implementation is to set the charset for MySQL and PostgreSQL database connections.
* @param PDO $pdo the PDO instance
*/
protected function initConnection($pdo)
{
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES'))
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
if ($this->charset !== null)
{
$driver = strtolower($pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
if (in_array($driver, array('pgsql', 'mysql', 'mysqli')))
$pdo->exec('SET NAMES ' . $pdo->quote($this->charset));
}
if ($this->initSQLs !== null)
{
foreach ($this->initSQLs as $sql)
$pdo->exec($sql);
}
}
/**
* Returns the PDO instance.
* @return PDO the PDO instance, null if the connection is not established yet
*/
public function getPdoInstance()
{
return $this->_pdo;
}
/**
* Creates a command for execution.
* @param mixed $query the DB query to be executed. This can be either a string representing a SQL statement,
* or an array representing different fragments of a SQL statement. Please refer to {@link CDbCommand::__construct}
* for more details about how to pass an array as the query. If this parameter is not given,
* you will have to call query builder methods of {@link CDbCommand} to build the DB query.
* @return CDbCommand the DB command
*/
public function createCommand($query = null)
{
$this->setActive(true);
return new CDbCommand($this, $query);
}
/**
* Returns the currently active transaction.
* @return CDbTransaction the currently active transaction. Null if no active transaction.
*/
public function getCurrentTransaction()
{
if ($this->_transaction !== null)
{
if ($this->_transaction->getActive())
return $this->_transaction;
}
return null;
}
/**
* Starts a transaction.
* @return CDbTransaction the transaction initiated
*/
public function beginTransaction()
{
Yii::trace('Starting transaction', 'system.db.Connection');
$this->setActive(true);
$this->_pdo->beginTransaction();
return $this->_transaction = new CDbTransaction($this);
}
/**
* Returns the database schema for the current connection
* @return CDbSchema the database schema for the current connection
*/
public function getSchema()
{
if ($this->_schema !== null)
return $this->_schema;
else
{
$driver = $this->getDriverName();
if (isset($this->driverMap[$driver]))
return $this->_schema = Yii::createComponent($this->driverMap[$driver], $this);
else
throw new CDbException(Yii::t('yii', 'Connection does not support reading schema for {driver} database.',
array('{driver}' => $driver)));
}
}
/**
* Returns the SQL command builder for the current DB connection.
* @return CDbCommandBuilder the command builder
* @since 1.0.4
*/
public function getCommandBuilder()
{
return $this->getSchema()->getCommandBuilder();
}
/**
* Returns the ID of the last inserted row or sequence value.
* @param string $sequenceName name of the sequence object (required by some DBMS)
* @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
* @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
*/
public function getLastInsertID($sequenceName = '')
{
$this->setActive(true);
return $this->_pdo->lastInsertId($sequenceName);
}
/**
* Quotes a string value for use in a query.
* @param string $str string to be quoted
* @return string the properly quoted string
* @see http://www.php.net/manual/en/function.PDO-quote.php
*/
public function quoteValue($str)
{
if (is_int($str) || is_float($str))
return $str;
$this->setActive(true);
if (($value = $this->_pdo->quote($str)) !== false)
return $value;
else // the driver doesn't support quote (e.g. oci)
return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
}
/**
* Quotes a table name for use in a query.
* If the table name contains schema prefix, the prefix will also be properly quoted.
* @param string $name table name
* @return string the properly quoted table name
*/
public function quoteTableName($name)
{
return $this->getSchema()->quoteTableName($name);
}
/**
* Quotes a column name for use in a query.
* If the column name contains prefix, the prefix will also be properly quoted.
* @param string $name column name
* @return string the properly quoted column name
*/
public function quoteColumnName($name)
{
return $this->getSchema()->quoteColumnName($name);
}
/**
* Determines the PDO type for the specified PHP type.
* @param string $type The PHP type (obtained by gettype() call).
* @return integer the corresponding PDO type
*/
public function getPdoType($type)
{
static $map = array
(
'boolean' => PDO::PARAM_BOOL,
'integer' => PDO::PARAM_INT,
'string' => PDO::PARAM_STR,
'NULL' => PDO::PARAM_NULL,
);
return isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
}
/**
* Returns the case of the column names
* @return mixed the case of the column names
* @see http://www.php.net/manual/en/pdo.setattribute.php
*/
public function getColumnCase()
{
return $this->getAttribute(PDO::ATTR_CASE);
}
/**
* Sets the case of the column names.
* @param mixed $value the case of the column names
* @see http://www.php.net/manual/en/pdo.setattribute.php
*/
public function setColumnCase($value)
{
$this->setAttribute(PDO::ATTR_CASE, $value);
}
/**
* Returns how the null and empty strings are converted.
* @return mixed how the null and empty strings are converted
* @see http://www.php.net/manual/en/pdo.setattribute.php
*/
public function getNullConversion()
{
return $this->getAttribute(PDO::ATTR_ORACLE_NULLS);
}
/**
* Sets how the null and empty strings are converted.
* @param mixed $value how the null and empty strings are converted
* @see http://www.php.net/manual/en/pdo.setattribute.php
*/
public function setNullConversion($value)
{
$this->setAttribute(PDO::ATTR_ORACLE_NULLS, $value);
}
/**
* Returns whether creating or updating a DB record will be automatically committed.
* Some DBMS (such as sqlite) may not support this feature.
* @return boolean whether creating or updating a DB record will be automatically committed.
*/
public function getAutoCommit()
{
return $this->getAttribute(PDO::ATTR_AUTOCOMMIT);
}
/**
* Sets whether creating or updating a DB record will be automatically committed.
* Some DBMS (such as sqlite) may not support this feature.
* @param boolean $value whether creating or updating a DB record will be automatically committed.
*/
public function setAutoCommit($value)
{
$this->setAttribute(PDO::ATTR_AUTOCOMMIT, $value);
}
/**
* Returns whether the connection is persistent or not.
* Some DBMS (such as sqlite) may not support this feature.
* @return boolean whether the connection is persistent or not
*/
public function getPersistent()
{
return $this->getAttribute(PDO::ATTR_PERSISTENT);
}
/**
* Sets whether the connection is persistent or not.
* Some DBMS (such as sqlite) may not support this feature.
* @param boolean $value whether the connection is persistent or not
*/
public function setPersistent($value)
{
return $this->setAttribute(PDO::ATTR_PERSISTENT, $value);
}
/**
* Returns the name of the DB driver
* @return string name of the DB driver
*/
public function getDriverName()
{
if (($pos = strpos($this->connectionString, ':')) !== false)
return strtolower(substr($this->connectionString, 0, $pos));
// return $this->getAttribute(PDO::ATTR_DRIVER_NAME);
}
/**
* Returns the version information of the DB driver.
* @return string the version information of the DB driver
*/
public function getClientVersion()
{
return $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
}
/**
* Returns the status of the connection.
* Some DBMS (such as sqlite) may not support this feature.
* @return string the status of the connection
*/
public function getConnectionStatus()
{
return $this->getAttribute(PDO::ATTR_CONNECTION_STATUS);
}
/**
* Returns whether the connection performs data prefetching.
* @return boolean whether the connection performs data prefetching
*/
public function getPrefetch()
{
return $this->getAttribute(PDO::ATTR_PREFETCH);
}
/**
* Returns the information of DBMS server.
* @return string the information of DBMS server
*/
public function getServerInfo()
{
return $this->getAttribute(PDO::ATTR_SERVER_INFO);
}
/**
* Returns the version information of DBMS server.
* @return string the version information of DBMS server
*/
public function getServerVersion()
{
return $this->getAttribute(PDO::ATTR_SERVER_VERSION);
}
/**
* Returns the timeout settings for the connection.
* @return integer timeout settings for the connection
*/
public function getTimeout()
{
return $this->getAttribute(PDO::ATTR_TIMEOUT);
}
/**
* Obtains a specific DB connection attribute information.
* @param integer $name the attribute to be queried
* @return mixed the corresponding attribute information
* @see http://www.php.net/manual/en/function.PDO-getAttribute.php
*/
public function getAttribute($name)
{
$this->setActive(true);
return $this->_pdo->getAttribute($name);
}
/**
* Sets an attribute on the database connection.
* @param integer $name the attribute to be set
* @param mixed $value the attribute value
* @see http://www.php.net/manual/en/function.PDO-setAttribute.php
*/
public function setAttribute($name, $value)
{
if ($this->_pdo instanceof PDO)
$this->_pdo->setAttribute($name, $value);
else
$this->_attributes[$name] = $value;
}
/**
* Returns the attributes that are previously explicitly set for the DB connection.
* @return array attributes (name=>value) that are previously explicitly set for the DB connection.
* @see setAttributes
* @since 1.1.7
*/
public function getAttributes()
{
return $this->_attributes;
}
/**
* Sets a set of attributes on the database connection.
* @param array $values attributes (name=>value) to be set.
* @see setAttribute
* @since 1.1.7
*/
public function setAttributes($values)
{
foreach ($values as $name => $value)
$this->_attributes[$name] = $value;
}
/**
* Returns the statistical results of SQL executions.
* The results returned include the number of SQL statements executed and
* the total time spent.
* In order to use this method, {@link enableProfiling} has to be set true.
* @return array the first element indicates the number of SQL statements executed,
* and the second element the total time spent in SQL execution.
* @since 1.0.6
*/
public function getStats()
{
$logger = Yii::getLogger();
$timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.query');
$count = count($timings);
$time = array_sum($timings);
$timings = $logger->getProfilingResults(null, 'system.db.CDbCommand.execute');
$count += count($timings);
$time += array_sum($timings);
return array($count, $time);
}
}
<?php
/**
* CDbDataReader class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbDataReader represents a forward-only stream of rows from a query result set.
*
* To read the current row of data, call {@link read}. The method {@link readAll}
* returns all the rows in a single array.
*
* One can also retrieve the rows of data in CDbDataReader by using foreach:
* <pre>
* foreach($reader as $row)
* // $row represents a row of data
* </pre>
* Since CDbDataReader is a forward-only stream, you can only traverse it once.
*
* It is possible to use a specific mode of data fetching by setting
* {@link setFetchMode FetchMode}. See {@link http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php}
* for more details.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbDataReader.php 3204 2011-05-05 21:36:32Z alexander.makarow $
* @package system.db
* @since 1.0
*/
class CDbDataReader extends CComponent implements Iterator, Countable
{
private $_statement;
private $_closed = false;
private $_row;
private $_index = -1;
/**
* Constructor.
* @param CDbCommand $command the command generating the query result
*/
public function __construct(CDbCommand $command)
{
$this->_statement = $command->getPdoStatement();
$this->_statement->setFetchMode(PDO::FETCH_ASSOC);
}
/**
* Binds a column to a PHP variable.
* When rows of data are being fetched, the corresponding column value
* will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND.
* @param mixed $column Number of the column (1-indexed) or name of the column
* in the result set. If using the column name, be aware that the name
* should match the case of the column, as returned by the driver.
* @param mixed $value Name of the PHP variable to which the column will be bound.
* @param integer $dataType Data type of the parameter
* @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php
*/
public function bindColumn($column, &$value, $dataType = null)
{
if ($dataType === null)
$this->_statement->bindColumn($column, $value);
else
$this->_statement->bindColumn($column, $value, $dataType);
}
/**
* Set the default fetch mode for this statement
* @param mixed $mode fetch mode
* @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
*/
public function setFetchMode($mode)
{
$params = func_get_args();
call_user_func_array(array($this->_statement, 'setFetchMode'), $params);
}
/**
* Advances the reader to the next row in a result set.
* @return array|false the current row, false if no more row available
*/
public function read()
{
return $this->_statement->fetch();
}
/**
* Returns a single column from the next row of a result set.
* @param integer $columnIndex zero-based column index
* @return mixed|false the column of the current row, false if no more row available
*/
public function readColumn($columnIndex)
{
return $this->_statement->fetchColumn($columnIndex);
}
/**
* Returns an object populated with the next row of data.
* @param string $className class name of the object to be created and populated
* @param array $fields Elements of this array are passed to the constructor
* @return mixed|false the populated object, false if no more row of data available
*/
public function readObject($className, $fields)
{
return $this->_statement->fetchObject($className, $fields);
}
/**
* Reads the whole result set into an array.
* @return array the result set (each array element represents a row of data).
* An empty array will be returned if the result contains no row.
*/
public function readAll()
{
return $this->_statement->fetchAll();
}
/**
* Advances the reader to the next result when reading the results of a batch of statements.
* This method is only useful when there are multiple result sets
* returned by the query. Not all DBMS support this feature.
* @return boolean Returns true on success or false on failure.
*/
public function nextResult()
{
if (($result = $this->_statement->nextRowset()) !== false)
$this->_index = -1;
return $result;
}
/**
* Closes the reader.
* This frees up the resources allocated for executing this SQL statement.
* Read attemps after this method call are unpredictable.
*/
public function close()
{
$this->_statement->closeCursor();
$this->_closed = true;
}
/**
* whether the reader is closed or not.
* @return boolean whether the reader is closed or not.
*/
public function getIsClosed()
{
return $this->_closed;
}
/**
* Returns the number of rows in the result set.
* Note, most DBMS may not give a meaningful count.
* In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
* @return integer number of rows contained in the result.
*/
public function getRowCount()
{
return $this->_statement->rowCount();
}
/**
* Returns the number of rows in the result set.
* This method is required by the Countable interface.
* Note, most DBMS may not give a meaningful count.
* In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
* @return integer number of rows contained in the result.
*/
public function count()
{
return $this->getRowCount();
}
/**
* Returns the number of columns in the result set.
* Note, even there's no row in the reader, this still gives correct column number.
* @return integer the number of columns in the result set.
*/
public function getColumnCount()
{
return $this->_statement->columnCount();
}
/**
* Resets the iterator to the initial state.
* This method is required by the interface Iterator.
* @throws CException if this method is invoked twice
*/
public function rewind()
{
if ($this->_index < 0)
{
$this->_row = $this->_statement->fetch();
$this->_index = 0;
}
else
throw new CDbException(Yii::t('yii', 'CDbDataReader cannot rewind. It is a forward-only reader.'));
}
/**
* Returns the index of the current row.
* This method is required by the interface Iterator.
* @return integer the index of the current row.
*/
public function key()
{
return $this->_index;
}
/**
* Returns the current row.
* This method is required by the interface Iterator.
* @return mixed the current row.
*/
public function current()
{
return $this->_row;
}
/**
* Moves the internal pointer to the next row.
* This method is required by the interface Iterator.
*/
public function next()
{
$this->_row = $this->_statement->fetch();
$this->_index++;
}
/**
* Returns whether there is a row of data at current position.
* This method is required by the interface Iterator.
* @return boolean whether there is a row of data at current position.
*/
public function valid()
{
return $this->_row !== false;
}
}
<?php
/**
* CDbTransaction class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2011 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbTransaction represents a DB transaction.
*
* It is usually created by calling {@link CDbConnection::beginTransaction}.
*
* The following code is a common scenario of using transactions:
* <pre>
* $transaction=$connection->beginTransaction();
* try
* {
* $connection->createCommand($sql1)->execute();
* $connection->createCommand($sql2)->execute();
* //.... other SQL executions
* $transaction->commit();
* }
* catch(Exception $e)
* {
* $transaction->rollBack();
* }
* </pre>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CDbTransaction.php 3069 2011-03-14 00:28:38Z qiang.xue $
* @package system.db
* @since 1.0
*/
class CDbTransaction extends CComponent
{
private $_connection = null;
private $_active;
/**
* Constructor.
* @param CDbConnection $connection the connection associated with this transaction
* @see CDbConnection::beginTransaction
*/
public function __construct(CDbConnection $connection)
{
$this->_connection = $connection;
$this->_active = true;
}
/**
* Commits a transaction.
* @throws CException if the transaction or the DB connection is not active.
*/
public function commit()
{
if ($this->_active && $this->_connection->getActive())
{
Yii::trace('Committing transaction', 'system.db.CDbTransaction');
$this->_connection->getPdoInstance()->commit();
$this->_active = false;
}
else
throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.'));
}
/**
* Rolls back a transaction.
* @throws CException if the transaction or the DB connection is not active.
*/
public function rollback()
{
if ($this->_active && $this->_connection->getActive())
{
Yii::trace('Rolling back transaction', 'system.db.CDbTransaction');
$this->_connection->getPdoInstance()->rollBack();
$this->_active = false;
}
else
throw new CDbException(Yii::t('yii', 'CDbTransaction is inactive and cannot perform commit or roll back operations.'));
}
/**
* @return CDbConnection the DB connection for this transaction
*/
public function getConnection()
{
return $this->_connection;
}
/**
* @return boolean whether this transaction is active
*/
public function getActive()
{
return $this->_active;
}
/**
* @param boolean $value whether this transaction is active
*/
protected function setActive($value)
{
$this->_active = $value;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment