Overview

Packages

  • application
    • commands
    • components
      • actions
      • filters
      • leftWidget
      • permissions
      • sortableWidget
      • util
      • webupdater
      • x2flow
        • actions
        • triggers
      • X2GridView
      • X2Settings
    • controllers
    • models
      • embedded
    • modules
      • accounts
        • controllers
        • models
      • actions
        • controllers
        • models
      • calendar
        • controllers
        • models
      • charts
        • models
      • contacts
        • controllers
        • models
      • docs
        • components
        • controllers
        • models
      • groups
        • controllers
        • models
      • marketing
        • components
        • controllers
        • models
      • media
        • controllers
        • models
      • mobile
        • components
      • opportunities
        • controllers
        • models
      • products
        • controllers
        • models
      • quotes
        • controllers
        • models
      • services
        • controllers
        • models
      • template
        • models
      • users
        • controllers
        • models
      • workflow
        • controllers
        • models
      • x2Leads
        • controllers
        • models
  • Net
  • None
  • PHP
  • system
    • base
    • caching
      • dependencies
    • collections
    • console
    • db
      • ar
      • schema
        • cubrid
        • mssql
        • mysql
        • oci
        • pgsql
        • sqlite
    • i18n
      • gettext
    • logging
    • test
    • utils
    • validators
    • web
      • actions
      • auth
      • filters
      • form
      • helpers
      • renderers
      • services
      • widgets
        • captcha
        • pagers
  • Text
    • Highlighter
  • zii
    • behaviors
    • widgets
      • grid
      • jui

Classes

  • ActionToRecord
  • CActiveFinder
  • CActiveRecord
  • CActiveRecordBehavior
  • CActiveRecordMetaData
  • CActiveRelation
  • CBaseActiveRelation
  • CBelongsToRelation
  • CHasManyRelation
  • CHasOneRelation
  • CJoinElement
  • CJoinQuery
  • CManyManyRelation
  • ContactsNameBehavior
  • CStatElement
  • CStatRelation
  • ERememberFiltersBehavior
  • FileSystemObjectBehavior
  • MobileLayouts
  • RecordAliases
  • RelationshipsBehavior
  • TopicReplies
  • X2ActiveRecord
  • X2Flow
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * CActiveRecord class file.
   4:  *
   5:  * @author Qiang Xue <qiang.xue@gmail.com>
   6:  * @link http://www.yiiframework.com/
   7:  * @copyright 2008-2013 Yii Software LLC
   8:  * @license http://www.yiiframework.com/license/
   9:  */
  10: 
  11: /**
  12:  * CActiveRecord is the base class for classes representing relational data.
  13:  *
  14:  * It implements the active record design pattern, a popular Object-Relational Mapping (ORM) technique.
  15:  * Please check {@link http://www.yiiframework.com/doc/guide/database.ar the Guide} for more details
  16:  * about this class.
  17:  *
  18:  * @property CDbCriteria $dbCriteria The query criteria that is associated with this model.
  19:  * This criteria is mainly used by {@link scopes named scope} feature to accumulate
  20:  * different criteria specifications.
  21:  * @property CActiveRecordMetaData $metaData The meta for this AR class.
  22:  * @property CDbConnection $dbConnection The database connection used by active record.
  23:  * @property CDbTableSchema $tableSchema The metadata of the table that this AR belongs to.
  24:  * @property CDbCommandBuilder $commandBuilder The command builder used by this AR.
  25:  * @property array $attributes Attribute values indexed by attribute names.
  26:  * @property boolean $isNewRecord Whether the record is new and should be inserted when calling {@link save}.
  27:  * This property is automatically set in constructor and {@link populateRecord}.
  28:  * Defaults to false, but it will be set to true if the instance is created using
  29:  * the new operator.
  30:  * @property mixed $primaryKey The primary key value. An array (column name=>column value) is returned if the primary key is composite.
  31:  * If primary key is not defined, null will be returned.
  32:  * @property mixed $oldPrimaryKey The old primary key value. An array (column name=>column value) is returned if the primary key is composite.
  33:  * If primary key is not defined, null will be returned.
  34:  * @property string $tableAlias The default table alias.
  35:  *
  36:  * @author Qiang Xue <qiang.xue@gmail.com>
  37:  * @package system.db.ar
  38:  * @since 1.0
  39:  */
  40: abstract class CActiveRecord extends CModel
  41: {
  42:     const BELONGS_TO='CBelongsToRelation';
  43:     const HAS_ONE='CHasOneRelation';
  44:     const HAS_MANY='CHasManyRelation';
  45:     const MANY_MANY='CManyManyRelation';
  46:     const STAT='CStatRelation';
  47: 
  48:     /**
  49:      * @var CDbConnection the default database connection for all active record classes.
  50:      * By default, this is the 'db' application component.
  51:      * @see getDbConnection
  52:      */
  53:     public static $db;
  54: 
  55:     private static $_models=array();            // class name => model
  56:     private static $_md=array();                // class name => meta data
  57: 
  58:     private $_new=false;                        // whether this instance is new or not
  59:     private $_attributes=array();               // attribute name => attribute value
  60:     private $_related=array();                  // attribute name => related objects
  61:     private $_c;                                // query criteria (used by finder only)
  62:     private $_pk;                               // old primary key value
  63:     private $_alias='t';                        // the table alias being used for query
  64: 
  65: 
  66:     /**
  67:      * Constructor.
  68:      * @param string $scenario scenario name. See {@link CModel::scenario} for more details about this parameter.
  69:      * Note: in order to setup initial model parameters use {@link init()} or {@link afterConstruct()}.
  70:      * Do NOT override the constructor unless it is absolutely necessary!
  71:      */
  72:     public function __construct($scenario='insert')
  73:     {
  74:         if($scenario===null) // internally used by populateRecord() and model()
  75:             return;
  76: 
  77:         $this->setScenario($scenario);
  78:         $this->setIsNewRecord(true);
  79:         $this->_attributes=$this->getMetaData()->attributeDefaults;
  80: 
  81:         $this->init();
  82: 
  83:         $this->attachBehaviors($this->behaviors());
  84:         $this->afterConstruct();
  85:     }
  86: 
  87:     /**
  88:      * Initializes this model.
  89:      * This method is invoked when an AR instance is newly created and has
  90:      * its {@link scenario} set.
  91:      * You may override this method to provide code that is needed to initialize the model (e.g. setting
  92:      * initial property values.)
  93:      */
  94:     public function init()
  95:     {
  96:     }
  97: 
  98:     /**
  99:      * Sets the parameters about query caching.
 100:      * This is a shortcut method to {@link CDbConnection::cache()}.
 101:      * It changes the query caching parameter of the {@link dbConnection} instance.
 102:      * @param integer $duration the number of seconds that query results may remain valid in cache.
 103:      * If this is 0, the caching will be disabled.
 104:      * @param CCacheDependency|ICacheDependency $dependency the dependency that will be used when saving
 105:      * the query results into cache.
 106:      * @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
 107:      * meaning that the next SQL query will be cached.
 108:      * @return static the active record instance itself.
 109:      * @since 1.1.7
 110:      */
 111:     public function cache($duration, $dependency=null, $queryCount=1)
 112:     {
 113:         $this->getDbConnection()->cache($duration, $dependency, $queryCount);
 114:         return $this;
 115:     }
 116: 
 117:     /**
 118:      * PHP sleep magic method.
 119:      * This method ensures that the model meta data reference is set to null.
 120:      * @return array
 121:      */
 122:     public function __sleep()
 123:     {
 124:         return array_keys((array)$this);
 125:     }
 126: 
 127:     /**
 128:      * PHP getter magic method.
 129:      * This method is overridden so that AR attributes can be accessed like properties.
 130:      * @param string $name property name
 131:      * @return mixed property value
 132:      * @see getAttribute
 133:      */
 134:     public function __get($name)
 135:     {
 136:         if(isset($this->_attributes[$name]))
 137:             return $this->_attributes[$name];
 138:         elseif(isset($this->getMetaData()->columns[$name]))
 139:             return null;
 140:         elseif(isset($this->_related[$name]))
 141:             return $this->_related[$name];
 142:         elseif(isset($this->getMetaData()->relations[$name]))
 143:             return $this->getRelated($name);
 144:         else
 145:             return parent::__get($name);
 146:     }
 147: 
 148:     /**
 149:      * PHP setter magic method.
 150:      * This method is overridden so that AR attributes can be accessed like properties.
 151:      * @param string $name property name
 152:      * @param mixed $value property value
 153:      */
 154:     public function __set($name,$value)
 155:     {
 156:         if($this->setAttribute($name,$value)===false)
 157:         {
 158:             if(isset($this->getMetaData()->relations[$name]))
 159:                 $this->_related[$name]=$value;
 160:             else
 161:                 parent::__set($name,$value);
 162:         }
 163:     }
 164: 
 165:     /**
 166:      * Checks if a property value is null.
 167:      * This method overrides the parent implementation by checking
 168:      * if the named attribute is null or not.
 169:      * @param string $name the property name or the event name
 170:      * @return boolean whether the property value is null
 171:      */
 172:     public function __isset($name)
 173:     {
 174:         if(isset($this->_attributes[$name]))
 175:             return true;
 176:         elseif(isset($this->getMetaData()->columns[$name]))
 177:             return false;
 178:         elseif(isset($this->_related[$name]))
 179:             return true;
 180:         elseif(isset($this->getMetaData()->relations[$name]))
 181:             return $this->getRelated($name)!==null;
 182:         else
 183:             return parent::__isset($name);
 184:     }
 185: 
 186:     /**
 187:      * Sets a component property to be null.
 188:      * This method overrides the parent implementation by clearing
 189:      * the specified attribute value.
 190:      * @param string $name the property name or the event name
 191:      */
 192:     public function __unset($name)
 193:     {
 194:         if(isset($this->getMetaData()->columns[$name]))
 195:             unset($this->_attributes[$name]);
 196:         elseif(isset($this->getMetaData()->relations[$name]))
 197:             unset($this->_related[$name]);
 198:         else
 199:             parent::__unset($name);
 200:     }
 201: 
 202:     /**
 203:      * Calls the named method which is not a class method.
 204:      * Do not call this method. This is a PHP magic method that we override
 205:      * to implement the named scope feature.
 206:      * @param string $name the method name
 207:      * @param array $parameters method parameters
 208:      * @return mixed the method return value
 209:      */
 210:     public function __call($name,$parameters)
 211:     {
 212:         if(isset($this->getMetaData()->relations[$name]))
 213:         {
 214:             if(empty($parameters))
 215:                 return $this->getRelated($name,false);
 216:             else
 217:                 return $this->getRelated($name,false,$parameters[0]);
 218:         }
 219: 
 220:         $scopes=$this->scopes();
 221:         if(isset($scopes[$name]))
 222:         {
 223:             $this->getDbCriteria()->mergeWith($scopes[$name]);
 224:             return $this;
 225:         }
 226: 
 227:         return parent::__call($name,$parameters);
 228:     }
 229: 
 230:     /**
 231:      * Returns the related record(s).
 232:      * This method will return the related record(s) of the current record.
 233:      * If the relation is HAS_ONE or BELONGS_TO, it will return a single object
 234:      * or null if the object does not exist.
 235:      * If the relation is HAS_MANY or MANY_MANY, it will return an array of objects
 236:      * or an empty array.
 237:      * @param string $name the relation name (see {@link relations})
 238:      * @param boolean $refresh whether to reload the related objects from database. Defaults to false.
 239:      * If the current record is not a new record and it does not have the related objects loaded they
 240:      * will be retrieved from the database even if this is set to false.
 241:      * If the current record is a new record and this value is false, the related objects will not be
 242:      * retrieved from the database.
 243:      * @param mixed $params array or CDbCriteria object with additional parameters that customize the query conditions as specified in the relation declaration.
 244:      * If this is supplied the related record(s) will be retrieved from the database regardless of the value or {@link $refresh}.
 245:      * The related record(s) retrieved when this is supplied will only be returned by this method and will not be loaded into the current record's relation.
 246:      * The value of the relation prior to running this method will still be available for the current record if this is supplied.
 247:      * @return mixed the related object(s).
 248:      * @throws CDbException if the relation is not specified in {@link relations}.
 249:      */
 250:     public function getRelated($name,$refresh=false,$params=array())
 251:     {
 252:         if(!$refresh && $params===array() && (isset($this->_related[$name]) || array_key_exists($name,$this->_related)))
 253:             return $this->_related[$name];
 254: 
 255:         $md=$this->getMetaData();
 256:         if(!isset($md->relations[$name]))
 257:             throw new CDbException(Yii::t('yii','{class} does not have relation "{name}".',
 258:                 array('{class}'=>get_class($this), '{name}'=>$name)));
 259: 
 260:         Yii::trace('lazy loading '.get_class($this).'.'.$name,'system.db.ar.CActiveRecord');
 261:         $relation=$md->relations[$name];
 262:         if($this->getIsNewRecord() && !$refresh && ($relation instanceof CHasOneRelation || $relation instanceof CHasManyRelation))
 263:             return $relation instanceof CHasOneRelation ? null : array();
 264: 
 265:         if($params!==array()) // dynamic query
 266:         {
 267:             $exists=isset($this->_related[$name]) || array_key_exists($name,$this->_related);
 268:             if($exists)
 269:                 $save=$this->_related[$name];
 270: 
 271:             if($params instanceof CDbCriteria)
 272:                 $params = $params->toArray();
 273: 
 274:             $r=array($name=>$params);
 275:         }
 276:         else
 277:             $r=$name;
 278:         unset($this->_related[$name]);
 279: 
 280:         $finder=$this->getActiveFinder($r);
 281:         $finder->lazyFind($this);
 282: 
 283:         if(!isset($this->_related[$name]))
 284:         {
 285:             if($relation instanceof CHasManyRelation)
 286:                 $this->_related[$name]=array();
 287:             elseif($relation instanceof CStatRelation)
 288:                 $this->_related[$name]=$relation->defaultValue;
 289:             else
 290:                 $this->_related[$name]=null;
 291:         }
 292: 
 293:         if($params!==array())
 294:         {
 295:             $results=$this->_related[$name];
 296:             if($exists)
 297:                 $this->_related[$name]=$save;
 298:             else
 299:                 unset($this->_related[$name]);
 300:             return $results;
 301:         }
 302:         else
 303:             return $this->_related[$name];
 304:     }
 305: 
 306:     /**
 307:      * Returns a value indicating whether the named related object(s) has been loaded.
 308:      * @param string $name the relation name
 309:      * @return boolean a value indicating whether the named related object(s) has been loaded.
 310:      */
 311:     public function hasRelated($name)
 312:     {
 313:         return isset($this->_related[$name]) || array_key_exists($name,$this->_related);
 314:     }
 315: 
 316:     /**
 317:      * Returns the query criteria associated with this model.
 318:      * @param boolean $createIfNull whether to create a criteria instance if it does not exist. Defaults to true.
 319:      * @return CDbCriteria the query criteria that is associated with this model.
 320:      * This criteria is mainly used by {@link scopes named scope} feature to accumulate
 321:      * different criteria specifications.
 322:      */
 323:     public function getDbCriteria($createIfNull=true)
 324:     {
 325:         if($this->_c===null)
 326:         {
 327:             if(($c=$this->defaultScope())!==array() || $createIfNull)
 328:                 $this->_c=new CDbCriteria($c);
 329:         }
 330:         return $this->_c;
 331:     }
 332: 
 333:     /**
 334:      * Sets the query criteria for the current model.
 335:      * @param CDbCriteria $criteria the query criteria
 336:      * @since 1.1.3
 337:      */
 338:     public function setDbCriteria($criteria)
 339:     {
 340:         $this->_c=$criteria;
 341:     }
 342: 
 343:     /**
 344:      * Returns the default named scope that should be implicitly applied to all queries for this model.
 345:      * Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.
 346:      * The default implementation simply returns an empty array. You may override this method
 347:      * if the model needs to be queried with some default criteria (e.g. only active records should be returned).
 348:      * @return array the query criteria. This will be used as the parameter to the constructor
 349:      * of {@link CDbCriteria}.
 350:      */
 351:     public function defaultScope()
 352:     {
 353:         return array();
 354:     }
 355: 
 356:     /**
 357:      * Resets all scopes and criterias applied.
 358:      *
 359:      * @param boolean $resetDefault including default scope. This parameter available since 1.1.12
 360:      * @return static the AR instance itself
 361:      * @since 1.1.2
 362:      */
 363:     public function resetScope($resetDefault=true)
 364:     {
 365:         if($resetDefault)
 366:             $this->_c=new CDbCriteria();
 367:         else
 368:             $this->_c=null;
 369: 
 370:         return $this;
 371:     }
 372: 
 373:     /**
 374:      * Returns the static model of the specified AR class.
 375:      * The model returned is a static instance of the AR class.
 376:      * It is provided for invoking class-level methods (something similar to static class methods.)
 377:      *
 378:      * EVERY derived AR class must override this method as follows,
 379:      * <pre>
 380:      * public static function model($className=__CLASS__)
 381:      * {
 382:      *     return parent::model($className);
 383:      * }
 384:      * </pre>
 385:      *
 386:      * @param string $className active record class name.
 387:      * @return static active record model instance.
 388:      */
 389:     public static function model($className=__CLASS__)
 390:     {
 391:         if(isset(self::$_models[$className]))
 392:             return self::$_models[$className];
 393:         else
 394:         {
 395:             $model=self::$_models[$className]=new $className(null);
 396:             $model->attachBehaviors($model->behaviors());
 397:             return $model;
 398:         }
 399:     }
 400: 
 401:     /**
 402:      * Returns the meta-data for this AR
 403:      * @return CActiveRecordMetaData the meta for this AR class.
 404:      */
 405:     public function getMetaData()
 406:     {
 407:         $className=get_class($this);
 408:         if(!array_key_exists($className,self::$_md))
 409:         {
 410:             self::$_md[$className]=null; // preventing recursive invokes of {@link getMetaData()} via {@link __get()}
 411:             self::$_md[$className]=new CActiveRecordMetaData($this);
 412:         }
 413:         return self::$_md[$className];
 414:     }
 415: 
 416:     /**
 417:      * Refreshes the meta data for this AR class.
 418:      * By calling this method, this AR class will regenerate the meta data needed.
 419:      * This is useful if the table schema has been changed and you want to use the latest
 420:      * available table schema. Make sure you have called {@link CDbSchema::refresh}
 421:      * before you call this method. Otherwise, old table schema data will still be used.
 422:      */
 423:     public function refreshMetaData()
 424:     {
 425:         $className=get_class($this);
 426:         if(array_key_exists($className,self::$_md))
 427:             unset(self::$_md[$className]);
 428:     }
 429: 
 430:     /**
 431:      * Returns the name of the associated database table.
 432:      * By default this method returns the class name as the table name.
 433:      * You may override this method if the table is not named after this convention.
 434:      * @return string the table name
 435:      */
 436:     public function tableName()
 437:     {
 438:         $tableName = get_class($this);
 439:         if(($pos=strrpos($tableName,'\\')) !== false)
 440:             return substr($tableName,$pos+1);
 441:         return $tableName;
 442:     }
 443: 
 444:     /**
 445:      * Returns the primary key of the associated database table.
 446:      * This method is meant to be overridden in case when the table is not defined with a primary key
 447:      * (for some legency database). If the table is already defined with a primary key,
 448:      * you do not need to override this method. The default implementation simply returns null,
 449:      * meaning using the primary key defined in the database.
 450:      * @return mixed the primary key of the associated database table.
 451:      * If the key is a single column, it should return the column name;
 452:      * If the key is a composite one consisting of several columns, it should
 453:      * return the array of the key column names.
 454:      */
 455:     public function primaryKey()
 456:     {
 457:     }
 458: 
 459:     /**
 460:      * This method should be overridden to declare related objects.
 461:      *
 462:      * There are four types of relations that may exist between two active record objects:
 463:      * <ul>
 464:      * <li>BELONGS_TO: e.g. a member belongs to a team;</li>
 465:      * <li>HAS_ONE: e.g. a member has at most one profile;</li>
 466:      * <li>HAS_MANY: e.g. a team has many members;</li>
 467:      * <li>MANY_MANY: e.g. a member has many skills and a skill belongs to a member.</li>
 468:      * </ul>
 469:      *
 470:      * Besides the above relation types, a special relation called STAT is also supported
 471:      * that can be used to perform statistical query (or aggregational query).
 472:      * It retrieves the aggregational information about the related objects, such as the number
 473:      * of comments for each post, the average rating for each product, etc.
 474:      *
 475:      * Each kind of related objects is defined in this method as an array with the following elements:
 476:      * <pre>
 477:      * 'varName'=>array('relationType', 'className', 'foreignKey', ...additional options)
 478:      * </pre>
 479:      * where 'varName' refers to the name of the variable/property that the related object(s) can
 480:      * be accessed through; 'relationType' refers to the type of the relation, which can be one of the
 481:      * following four constants: self::BELONGS_TO, self::HAS_ONE, self::HAS_MANY and self::MANY_MANY;
 482:      * 'className' refers to the name of the active record class that the related object(s) is of;
 483:      * and 'foreignKey' states the foreign key that relates the two kinds of active record.
 484:      * Note, for composite foreign keys, they can be either listed together, separated by commas or specified as an array
 485:      * in format of array('key1','key2'). In case you need to specify custom PK->FK association you can define it as
 486:      * array('fk'=>'pk'). For composite keys it will be array('fk_c1'=>'pk_с1','fk_c2'=>'pk_c2').
 487:      * For foreign keys used in MANY_MANY relation, the joining table must be declared as well
 488:      * (e.g. 'join_table(fk1, fk2)').
 489:      *
 490:      * Additional options may be specified as name-value pairs in the rest array elements:
 491:      * <ul>
 492:      * <li>'select': string|array, a list of columns to be selected. Defaults to '*', meaning all columns.
 493:      *   Column names should be disambiguated if they appear in an expression (e.g. COUNT(relationName.name) AS name_count).</li>
 494:      * <li>'condition': string, the WHERE clause. Defaults to empty. Note, column references need to
 495:      *   be disambiguated with prefix 'relationName.' (e.g. relationName.age&gt;20)</li>
 496:      * <li>'order': string, the ORDER BY clause. Defaults to empty. Note, column references need to
 497:      *   be disambiguated with prefix 'relationName.' (e.g. relationName.age DESC)</li>
 498:      * <li>'with': string|array, a list of child related objects that should be loaded together with this object.
 499:      *   Note, this is only honored by lazy loading, not eager loading.</li>
 500:      * <li>'joinType': type of join. Defaults to 'LEFT OUTER JOIN'.</li>
 501:      * <li>'alias': the alias for the table associated with this relationship.
 502:      *   It defaults to null,
 503:      *   meaning the table alias is the same as the relation name.</li>
 504:      * <li>'params': the parameters to be bound to the generated SQL statement.
 505:      *   This should be given as an array of name-value pairs.</li>
 506:      * <li>'on': the ON clause. The condition specified here will be appended
 507:      *   to the joining condition using the AND operator.</li>
 508:      * <li>'index': the name of the column whose values should be used as keys
 509:      *   of the array that stores related objects. This option is only available to
 510:      *   HAS_MANY and MANY_MANY relations.</li>
 511:      * <li>'scopes': scopes to apply. In case of a single scope can be used like 'scopes'=>'scopeName',
 512:      *   in case of multiple scopes can be used like 'scopes'=>array('scopeName1','scopeName2').
 513:      *   This option has been available since version 1.1.9.</li>
 514:      * </ul>
 515:      *
 516:      * The following options are available for certain relations when lazy loading:
 517:      * <ul>
 518:      * <li>'group': string, the GROUP BY clause. Defaults to empty. Note, column references need to
 519:      *   be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
 520:      * <li>'having': string, the HAVING clause. Defaults to empty. Note, column references need to
 521:      *   be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
 522:      * <li>'limit': limit of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
 523:      * <li>'offset': offset of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
 524:      * <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>
 525:      * </ul>
 526:      *
 527:      * Below is an example declaring related objects for 'Post' active record class:
 528:      * <pre>
 529:      * return array(
 530:      *     'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
 531:      *     'comments'=>array(self::HAS_MANY, 'Comment', 'post_id', 'with'=>'author', 'order'=>'create_time DESC'),
 532:      *     'tags'=>array(self::MANY_MANY, 'Tag', 'post_tag(post_id, tag_id)', 'order'=>'name'),
 533:      * );
 534:      * </pre>
 535:      *
 536:      * @return array list of related object declarations. Defaults to empty array.
 537:      */
 538:     public function relations()
 539:     {
 540:         return array();
 541:     }
 542: 
 543:     /**
 544:      * Returns the declaration of named scopes.
 545:      * A named scope represents a query criteria that can be chained together with
 546:      * other named scopes and applied to a query. This method should be overridden
 547:      * by child classes to declare named scopes for the particular AR classes.
 548:      * For example, the following code declares two named scopes: 'recently' and
 549:      * 'published'.
 550:      * <pre>
 551:      * return array(
 552:      *     'published'=>array(
 553:      *           'condition'=>'status=1',
 554:      *     ),
 555:      *     'recently'=>array(
 556:      *           'order'=>'create_time DESC',
 557:      *           'limit'=>5,
 558:      *     ),
 559:      * );
 560:      * </pre>
 561:      * If the above scopes are declared in a 'Post' model, we can perform the following
 562:      * queries:
 563:      * <pre>
 564:      * $posts=Post::model()->published()->findAll();
 565:      * $posts=Post::model()->published()->recently()->findAll();
 566:      * $posts=Post::model()->published()->with('comments')->findAll();
 567:      * </pre>
 568:      * Note that the last query is a relational query.
 569:      *
 570:      * @return array the scope definition. The array keys are scope names; the array
 571:      * values are the corresponding scope definitions. Each scope definition is represented
 572:      * as an array whose keys must be properties of {@link CDbCriteria}.
 573:      */
 574:     public function scopes()
 575:     {
 576:         return array();
 577:     }
 578: 
 579:     /**
 580:      * Returns the list of all attribute names of the model.
 581:      * This would return all column names of the table associated with this AR class.
 582:      * @return array list of attribute names.
 583:      */
 584:     public function attributeNames()
 585:     {
 586:         return array_keys($this->getMetaData()->columns);
 587:     }
 588: 
 589:     /**
 590:      * Returns the text label for the specified attribute.
 591:      * This method overrides the parent implementation by supporting
 592:      * returning the label defined in relational object.
 593:      * In particular, if the attribute name is in the form of "post.author.name",
 594:      * then this method will derive the label from the "author" relation's "name" attribute.
 595:      * @param string $attribute the attribute name
 596:      * @return string the attribute label
 597:      * @see generateAttributeLabel
 598:      * @since 1.1.4
 599:      */
 600:     public function getAttributeLabel($attribute)
 601:     {
 602:         $labels=$this->attributeLabels();
 603:         if(isset($labels[$attribute]))
 604:             return $labels[$attribute];
 605:         elseif(strpos($attribute,'.')!==false)
 606:         {
 607:             $segs=explode('.',$attribute);
 608:             $name=array_pop($segs);
 609:             $model=$this;
 610:             foreach($segs as $seg)
 611:             {
 612:                 $relations=$model->getMetaData()->relations;
 613:                 if(isset($relations[$seg]))
 614:                     $model=CActiveRecord::model($relations[$seg]->className);
 615:                 else
 616:                     break;
 617:             }
 618:             return $model->getAttributeLabel($name);
 619:         }
 620:         else
 621:             return $this->generateAttributeLabel($attribute);
 622:     }
 623: 
 624:     /**
 625:      * Returns the database connection used by active record.
 626:      * By default, the "db" application component is used as the database connection.
 627:      * You may override this method if you want to use a different database connection.
 628:      * @throws CDbException if "db" application component is not defined
 629:      * @return CDbConnection the database connection used by active record.
 630:      */
 631:     public function getDbConnection()
 632:     {
 633:         if(self::$db!==null)
 634:             return self::$db;
 635:         else
 636:         {
 637:             self::$db=Yii::app()->getDb();
 638:             if(self::$db instanceof CDbConnection)
 639:                 return self::$db;
 640:             else
 641:                 throw new CDbException(Yii::t('yii','Active Record requires a "db" CDbConnection application component.'));
 642:         }
 643:     }
 644: 
 645:     /**
 646:      * Returns the named relation declared for this AR class.
 647:      * @param string $name the relation name
 648:      * @return CActiveRelation the named relation declared for this AR class. Null if the relation does not exist.
 649:      */
 650:     public function getActiveRelation($name)
 651:     {
 652:         return isset($this->getMetaData()->relations[$name]) ? $this->getMetaData()->relations[$name] : null;
 653:     }
 654: 
 655:     /**
 656:      * Returns the metadata of the table that this AR belongs to
 657:      * @return CDbTableSchema the metadata of the table that this AR belongs to
 658:      */
 659:     public function getTableSchema()
 660:     {
 661:         return $this->getMetaData()->tableSchema;
 662:     }
 663: 
 664:     /**
 665:      * Returns the command builder used by this AR.
 666:      * @return CDbCommandBuilder the command builder used by this AR
 667:      */
 668:     public function getCommandBuilder()
 669:     {
 670:         return $this->getDbConnection()->getSchema()->getCommandBuilder();
 671:     }
 672: 
 673:     /**
 674:      * Checks whether this AR has the named attribute
 675:      * @param string $name attribute name
 676:      * @return boolean whether this AR has the named attribute (table column).
 677:      */
 678:     public function hasAttribute($name)
 679:     {
 680:         return isset($this->getMetaData()->columns[$name]);
 681:     }
 682: 
 683:     /**
 684:      * Returns the named attribute value.
 685:      * If this is a new record and the attribute is not set before,
 686:      * the default column value will be returned.
 687:      * If this record is the result of a query and the attribute is not loaded,
 688:      * null will be returned.
 689:      * You may also use $this->AttributeName to obtain the attribute value.
 690:      * @param string $name the attribute name
 691:      * @return mixed the attribute value. Null if the attribute is not set or does not exist.
 692:      * @see hasAttribute
 693:      */
 694:     public function getAttribute($name)
 695:     {
 696:         if(property_exists($this,$name))
 697:             return $this->$name;
 698:         elseif(isset($this->_attributes[$name]))
 699:             return $this->_attributes[$name];
 700:     }
 701: 
 702:     /**
 703:      * Sets the named attribute value.
 704:      * You may also use $this->AttributeName to set the attribute value.
 705:      * @param string $name the attribute name
 706:      * @param mixed $value the attribute value.
 707:      * @return boolean whether the attribute exists and the assignment is conducted successfully
 708:      * @see hasAttribute
 709:      */
 710:     public function setAttribute($name,$value)
 711:     {
 712:         if(property_exists($this,$name))
 713:             $this->$name=$value;
 714:         elseif(isset($this->getMetaData()->columns[$name]))
 715:             $this->_attributes[$name]=$value;
 716:         else
 717:             return false;
 718:         return true;
 719:     }
 720: 
 721:     /**
 722:      * Do not call this method. This method is used internally by {@link CActiveFinder} to populate
 723:      * related objects. This method adds a related object to this record.
 724:      * @param string $name attribute name
 725:      * @param mixed $record the related record
 726:      * @param mixed $index the index value in the related object collection.
 727:      * If true, it means using zero-based integer index.
 728:      * If false, it means a HAS_ONE or BELONGS_TO object and no index is needed.
 729:      */
 730:     public function addRelatedRecord($name,$record,$index)
 731:     {
 732:         if($index!==false)
 733:         {
 734:             if(!isset($this->_related[$name]))
 735:                 $this->_related[$name]=array();
 736:             if($record instanceof CActiveRecord)
 737:             {
 738:                 if($index===true)
 739:                     $this->_related[$name][]=$record;
 740:                 else
 741:                     $this->_related[$name][$index]=$record;
 742:             }
 743:         }
 744:         elseif(!isset($this->_related[$name]))
 745:             $this->_related[$name]=$record;
 746:     }
 747: 
 748:     /**
 749:      * Returns all column attribute values.
 750:      * Note, related objects are not returned.
 751:      * @param mixed $names names of attributes whose value needs to be returned.
 752:      * If this is true (default), then all attribute values will be returned, including
 753:      * those that are not loaded from DB (null will be returned for those attributes).
 754:      * If this is null, all attributes except those that are not loaded from DB will be returned.
 755:      * @return array attribute values indexed by attribute names.
 756:      */
 757:     public function getAttributes($names=true)
 758:     {
 759:         $attributes=$this->_attributes;
 760:         foreach($this->getMetaData()->columns as $name=>$column)
 761:         {
 762:             if(property_exists($this,$name))
 763:                 $attributes[$name]=$this->$name;
 764:             elseif($names===true && !isset($attributes[$name]))
 765:                 $attributes[$name]=null;
 766:         }
 767:         if(is_array($names))
 768:         {
 769:             $attrs=array();
 770:             foreach($names as $name)
 771:             {
 772:                 if(property_exists($this,$name))
 773:                     $attrs[$name]=$this->$name;
 774:                 else
 775:                     $attrs[$name]=isset($attributes[$name])?$attributes[$name]:null;
 776:             }
 777:             return $attrs;
 778:         }
 779:         else
 780:             return $attributes;
 781:     }
 782: 
 783:     /**
 784:      * Saves the current record.
 785:      *
 786:      * The record is inserted as a row into the database table if its {@link isNewRecord}
 787:      * property is true (usually the case when the record is created using the 'new'
 788:      * operator). Otherwise, it will be used to update the corresponding row in the table
 789:      * (usually the case if the record is obtained using one of those 'find' methods.)
 790:      *
 791:      * Validation will be performed before saving the record. If the validation fails,
 792:      * the record will not be saved. You can call {@link getErrors()} to retrieve the
 793:      * validation errors.
 794:      *
 795:      * If the record is saved via insertion, its {@link isNewRecord} property will be
 796:      * set false, and its {@link scenario} property will be set to be 'update'.
 797:      * And if its primary key is auto-incremental and is not set before insertion,
 798:      * the primary key will be populated with the automatically generated key value.
 799:      *
 800:      * @param boolean $runValidation whether to perform validation before saving the record.
 801:      * If the validation fails, the record will not be saved to database.
 802:      * @param array $attributes list of attributes that need to be saved. Defaults to null,
 803:      * meaning all attributes that are loaded from DB will be saved.
 804:      * @return boolean whether the saving succeeds
 805:      */
 806:     public function save($runValidation=true,$attributes=null)
 807:     {
 808:         if(!$runValidation || $this->validate($attributes))
 809:             return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
 810:         else
 811:             return false;
 812:     }
 813: 
 814:     /**
 815:      * Returns if the current record is new.
 816:      * @return boolean whether the record is new and should be inserted when calling {@link save}.
 817:      * This property is automatically set in constructor and {@link populateRecord}.
 818:      * Defaults to false, but it will be set to true if the instance is created using
 819:      * the new operator.
 820:      */
 821:     public function getIsNewRecord()
 822:     {
 823:         return $this->_new;
 824:     }
 825: 
 826:     /**
 827:      * Sets if the record is new.
 828:      * @param boolean $value whether the record is new and should be inserted when calling {@link save}.
 829:      * @see getIsNewRecord
 830:      */
 831:     public function setIsNewRecord($value)
 832:     {
 833:         $this->_new=$value;
 834:     }
 835: 
 836:     /**
 837:      * This event is raised before the record is saved.
 838:      * By setting {@link CModelEvent::isValid} to be false, the normal {@link save()} process will be stopped.
 839:      * @param CModelEvent $event the event parameter
 840:      */
 841:     public function onBeforeSave($event)
 842:     {
 843:         $this->raiseEvent('onBeforeSave',$event);
 844:     }
 845: 
 846:     /**
 847:      * This event is raised after the record is saved.
 848:      * @param CEvent $event the event parameter
 849:      */
 850:     public function onAfterSave($event)
 851:     {
 852:         $this->raiseEvent('onAfterSave',$event);
 853:     }
 854: 
 855:     /**
 856:      * This event is raised before the record is deleted.
 857:      * By setting {@link CModelEvent::isValid} to be false, the normal {@link delete()} process will be stopped.
 858:      * @param CModelEvent $event the event parameter
 859:      */
 860:     public function onBeforeDelete($event)
 861:     {
 862:         $this->raiseEvent('onBeforeDelete',$event);
 863:     }
 864: 
 865:     /**
 866:      * This event is raised after the record is deleted.
 867:      * @param CEvent $event the event parameter
 868:      */
 869:     public function onAfterDelete($event)
 870:     {
 871:         $this->raiseEvent('onAfterDelete',$event);
 872:     }
 873: 
 874:     /**
 875:      * This event is raised before an AR finder performs a find call.
 876:      * This can be either a call to CActiveRecords find methods or a find call
 877:      * when model is loaded in relational context via lazy or eager loading.
 878:      * If you want to access or modify the query criteria used for the
 879:      * find call, you can use {@link getDbCriteria()} to customize it based on your needs.
 880:      * When modifying criteria in beforeFind you have to make sure you are using the right
 881:      * table alias which is different on normal find and relational call.
 882:      * You can use {@link getTableAlias()} to get the alias used for the upcoming find call.
 883:      * Please note that modification of criteria is fully supported as of version 1.1.13.
 884:      * Earlier versions had some problems with relational context and applying changes correctly.
 885:      * @param CModelEvent $event the event parameter
 886:      * @see beforeFind
 887:      */
 888:     public function onBeforeFind($event)
 889:     {
 890:         $this->raiseEvent('onBeforeFind',$event);
 891:     }
 892: 
 893:     /**
 894:      * This event is raised after the record is instantiated by a find method.
 895:      * @param CEvent $event the event parameter
 896:      */
 897:     public function onAfterFind($event)
 898:     {
 899:         $this->raiseEvent('onAfterFind',$event);
 900:     }
 901: 
 902:     /**
 903:      * Given 'with' options returns a new active finder instance.
 904:      *
 905:      * @param mixed $with the relation names to be actively looked for
 906:      * @return CActiveFinder active finder for the operation
 907:      *
 908:      * @since 1.1.14
 909:      */
 910:     public function getActiveFinder($with)
 911:     {
 912:         return new CActiveFinder($this,$with);
 913:     }
 914: 
 915:     /**
 916:      * This event is raised before an AR finder performs a count call.
 917:      * If you want to access or modify the query criteria used for the
 918:      * count call, you can use {@link getDbCriteria()} to customize it based on your needs.
 919:      * When modifying criteria in beforeCount you have to make sure you are using the right
 920:      * table alias which is different on normal count and relational call.
 921:      * You can use {@link getTableAlias()} to get the alias used for the upcoming count call.
 922:      * @param CModelEvent $event the event parameter
 923:      * @see beforeCount
 924:      * @since 1.1.14
 925:      */
 926:     public function onBeforeCount($event)
 927:     {
 928:         $this->raiseEvent('onBeforeCount',$event);
 929:     }
 930: 
 931:     /**
 932:      * This method is invoked before saving a record (after validation, if any).
 933:      * The default implementation raises the {@link onBeforeSave} event.
 934:      * You may override this method to do any preparation work for record saving.
 935:      * Use {@link isNewRecord} to determine whether the saving is
 936:      * for inserting or updating record.
 937:      * Make sure you call the parent implementation so that the event is raised properly.
 938:      * @return boolean whether the saving should be executed. Defaults to true.
 939:      */
 940:     protected function beforeSave()
 941:     {
 942:         if($this->hasEventHandler('onBeforeSave'))
 943:         {
 944:             $event=new CModelEvent($this);
 945:             $this->onBeforeSave($event);
 946:             return $event->isValid;
 947:         }
 948:         else
 949:             return true;
 950:     }
 951: 
 952:     /**
 953:      * This method is invoked after saving a record successfully.
 954:      * The default implementation raises the {@link onAfterSave} event.
 955:      * You may override this method to do postprocessing after record saving.
 956:      * Make sure you call the parent implementation so that the event is raised properly.
 957:      */
 958:     protected function afterSave()
 959:     {
 960:         if($this->hasEventHandler('onAfterSave'))
 961:             $this->onAfterSave(new CEvent($this));
 962:     }
 963: 
 964:     /**
 965:      * This method is invoked before deleting a record.
 966:      * The default implementation raises the {@link onBeforeDelete} event.
 967:      * You may override this method to do any preparation work for record deletion.
 968:      * Make sure you call the parent implementation so that the event is raised properly.
 969:      * @return boolean whether the record should be deleted. Defaults to true.
 970:      */
 971:     protected function beforeDelete()
 972:     {
 973:         if($this->hasEventHandler('onBeforeDelete'))
 974:         {
 975:             $event=new CModelEvent($this);
 976:             $this->onBeforeDelete($event);
 977:             return $event->isValid;
 978:         }
 979:         else
 980:             return true;
 981:     }
 982: 
 983:     /**
 984:      * This method is invoked after deleting a record.
 985:      * The default implementation raises the {@link onAfterDelete} event.
 986:      * You may override this method to do postprocessing after the record is deleted.
 987:      * Make sure you call the parent implementation so that the event is raised properly.
 988:      */
 989:     protected function afterDelete()
 990:     {
 991:         if($this->hasEventHandler('onAfterDelete'))
 992:             $this->onAfterDelete(new CEvent($this));
 993:     }
 994: 
 995:     /**
 996:      * This method is invoked before an AR finder executes a find call.
 997:      * The find calls include {@link find}, {@link findAll}, {@link findByPk},
 998:      * {@link findAllByPk}, {@link findByAttributes}, {@link findAllByAttributes},
 999:      * {@link findBySql} and {@link findAllBySql}.
1000:      * The default implementation raises the {@link onBeforeFind} event.
1001:      * If you override this method, make sure you call the parent implementation
1002:      * so that the event is raised properly.
1003:      * For details on modifying query criteria see {@link onBeforeFind} event.
1004:      */
1005:     protected function beforeFind()
1006:     {
1007:         if($this->hasEventHandler('onBeforeFind'))
1008:         {
1009:             $event=new CModelEvent($this);
1010:             $this->onBeforeFind($event);
1011:         }
1012:     }
1013: 
1014:     /**
1015:      * This method is invoked before an AR finder executes a count call.
1016:      * The count calls include {@link count} and {@link countByAttributes}
1017:      * The default implementation raises the {@link onBeforeCount} event.
1018:      * If you override this method, make sure you call the parent implementation
1019:      * so that the event is raised properly.
1020:      * @since 1.1.14
1021:      */
1022:     protected function beforeCount()
1023:     {
1024:         if($this->hasEventHandler('onBeforeCount'))
1025:             $this->onBeforeCount(new CEvent($this));
1026:     }
1027: 
1028:     /**
1029:      * This method is invoked after each record is instantiated by a find method.
1030:      * The default implementation raises the {@link onAfterFind} event.
1031:      * You may override this method to do postprocessing after each newly found record is instantiated.
1032:      * Make sure you call the parent implementation so that the event is raised properly.
1033:      */
1034:     protected function afterFind()
1035:     {
1036:         if($this->hasEventHandler('onAfterFind'))
1037:             $this->onAfterFind(new CEvent($this));
1038:     }
1039: 
1040:     /**
1041:      * Calls {@link beforeFind}.
1042:      * This method is internally used.
1043:      */
1044:     public function beforeFindInternal()
1045:     {
1046:         $this->beforeFind();
1047:     }
1048: 
1049:     /**
1050:      * Calls {@link afterFind}.
1051:      * This method is internally used.
1052:      */
1053:     public function afterFindInternal()
1054:     {
1055:         $this->afterFind();
1056:     }
1057: 
1058:     /**
1059:      * Inserts a row into the table based on this active record attributes.
1060:      * If the table's primary key is auto-incremental and is null before insertion,
1061:      * it will be populated with the actual value after insertion.
1062:      * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
1063:      * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
1064:      * and its {@link scenario} property will be set to be 'update'.
1065:      * @param array $attributes list of attributes that need to be saved. Defaults to null,
1066:      * meaning all attributes that are loaded from DB will be saved.
1067:      * @return boolean whether the attributes are valid and the record is inserted successfully.
1068:      * @throws CDbException if the record is not new
1069:      */
1070:     public function insert($attributes=null)
1071:     {
1072:         if(!$this->getIsNewRecord())
1073:             throw new CDbException(Yii::t('yii','The active record cannot be inserted to database because it is not new.'));
1074:         if($this->beforeSave())
1075:         {
1076:             Yii::trace(get_class($this).'.insert()','system.db.ar.CActiveRecord');
1077:             $builder=$this->getCommandBuilder();
1078:             $table=$this->getTableSchema();
1079:             $command=$builder->createInsertCommand($table,$this->getAttributes($attributes));
1080:             if($command->execute())
1081:             {
1082:                 $primaryKey=$table->primaryKey;
1083:                 if($table->sequenceName!==null)
1084:                 {
1085:                     if(is_string($primaryKey) && $this->$primaryKey===null)
1086:                         $this->$primaryKey=$builder->getLastInsertID($table);
1087:                     elseif(is_array($primaryKey))
1088:                     {
1089:                         foreach($primaryKey as $pk)
1090:                         {
1091:                             if($this->$pk===null)
1092:                             {
1093:                                 $this->$pk=$builder->getLastInsertID($table);
1094:                                 break;
1095:                             }
1096:                         }
1097:                     }
1098:                 }
1099:                 $this->_pk=$this->getPrimaryKey();
1100:                 $this->afterSave();
1101:                 $this->setIsNewRecord(false);
1102:                 $this->setScenario('update');
1103:                 return true;
1104:             }
1105:         }
1106:         return false;
1107:     }
1108: 
1109:     /**
1110:      * Updates the row represented by this active record.
1111:      * All loaded attributes will be saved to the database.
1112:      * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
1113:      * @param array $attributes list of attributes that need to be saved. Defaults to null,
1114:      * meaning all attributes that are loaded from DB will be saved.
1115:      * @return boolean whether the update is successful
1116:      * @throws CDbException if the record is new
1117:      */
1118:     public function update($attributes=null)
1119:     {
1120:         if($this->getIsNewRecord())
1121:             throw new CDbException(Yii::t('yii','The active record cannot be updated because it is new.'));
1122:         if($this->beforeSave())
1123:         {
1124:             Yii::trace(get_class($this).'.update()','system.db.ar.CActiveRecord');
1125:             if($this->_pk===null)
1126:                 $this->_pk=$this->getPrimaryKey();
1127:             $this->updateByPk($this->getOldPrimaryKey(),$this->getAttributes($attributes));
1128:             $this->_pk=$this->getPrimaryKey();
1129:             $this->afterSave();
1130:             return true;
1131:         }
1132:         else
1133:             return false;
1134:     }
1135: 
1136:     /**
1137:      * Saves a selected list of attributes.
1138:      * Unlike {@link save}, this method only saves the specified attributes
1139:      * of an existing row dataset and does NOT call either {@link beforeSave} or {@link afterSave}.
1140:      * Also note that this method does neither attribute filtering nor validation.
1141:      * So do not use this method with untrusted data (such as user posted data).
1142:      * You may consider the following alternative if you want to do so:
1143:      * <pre>
1144:      * $postRecord=Post::model()->findByPk($postID);
1145:      * $postRecord->attributes=$_POST['post'];
1146:      * $postRecord->save();
1147:      * </pre>
1148:      * @param array $attributes attributes to be updated. Each element represents an attribute name
1149:      * or an attribute value indexed by its name. If the latter, the record's
1150:      * attribute will be changed accordingly before saving.
1151:      * @throws CDbException if the record is new
1152:      * @return boolean whether the update is successful. Note that false is also returned if the saving
1153:      * was successfull but no attributes had changed and the database driver returns 0 for the number
1154:      * of updated records.
1155:      */
1156:     public function saveAttributes($attributes)
1157:     {
1158:         if(!$this->getIsNewRecord())
1159:         {
1160:             Yii::trace(get_class($this).'.saveAttributes()','system.db.ar.CActiveRecord');
1161:             $values=array();
1162:             foreach($attributes as $name=>$value)
1163:             {
1164:                 if(is_integer($name))
1165:                     $values[$value]=$this->$value;
1166:                 else
1167:                     $values[$name]=$this->$name=$value;
1168:             }
1169:             if($this->_pk===null)
1170:                 $this->_pk=$this->getPrimaryKey();
1171:             if($this->updateByPk($this->getOldPrimaryKey(),$values)>0)
1172:             {
1173:                 $this->_pk=$this->getPrimaryKey();
1174:                 return true;
1175:             }
1176:             else
1177:                 return false;
1178:         }
1179:         else
1180:             throw new CDbException(Yii::t('yii','The active record cannot be updated because it is new.'));
1181:     }
1182: 
1183:     /**
1184:      * Saves one or several counter columns for the current AR object.
1185:      * Note that this method differs from {@link updateCounters} in that it only
1186:      * saves the current AR object.
1187:      * An example usage is as follows:
1188:      * <pre>
1189:      * $postRecord=Post::model()->findByPk($postID);
1190:      * $postRecord->saveCounters(array('view_count'=>1));
1191:      * </pre>
1192:      * Use negative values if you want to decrease the counters.
1193:      * @param array $counters the counters to be updated (column name=>increment value)
1194:      * @return boolean whether the saving is successful
1195:      * @see updateCounters
1196:      * @since 1.1.8
1197:      */
1198:     public function saveCounters($counters)
1199:     {
1200:         Yii::trace(get_class($this).'.saveCounters()','system.db.ar.CActiveRecord');
1201:         $builder=$this->getCommandBuilder();
1202:         $table=$this->getTableSchema();
1203:         $criteria=$builder->createPkCriteria($table,$this->getOldPrimaryKey());
1204:         $command=$builder->createUpdateCounterCommand($this->getTableSchema(),$counters,$criteria);
1205:         if($command->execute())
1206:         {
1207:             foreach($counters as $name=>$value)
1208:                 $this->$name=$this->$name+$value;
1209:             return true;
1210:         }
1211:         else
1212:             return false;
1213:     }
1214: 
1215:     /**
1216:      * Deletes the row corresponding to this active record.
1217:      * @throws CDbException if the record is new
1218:      * @return boolean whether the deletion is successful.
1219:      */
1220:     public function delete()
1221:     {
1222:         if(!$this->getIsNewRecord())
1223:         {
1224:             Yii::trace(get_class($this).'.delete()','system.db.ar.CActiveRecord');
1225:             if($this->beforeDelete())
1226:             {
1227:                 $result=$this->deleteByPk($this->getPrimaryKey())>0;
1228:                 $this->afterDelete();
1229:                 return $result;
1230:             }
1231:             else
1232:                 return false;
1233:         }
1234:         else
1235:             throw new CDbException(Yii::t('yii','The active record cannot be deleted because it is new.'));
1236:     }
1237: 
1238:     /**
1239:      * Repopulates this active record with the latest data.
1240:      * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
1241:      */
1242:     public function refresh()
1243:     {
1244:         Yii::trace(get_class($this).'.refresh()','system.db.ar.CActiveRecord');
1245:         if(($record=$this->findByPk($this->getPrimaryKey()))!==null)
1246:         {
1247:             $this->_attributes=array();
1248:             $this->_related=array();
1249:             foreach($this->getMetaData()->columns as $name=>$column)
1250:             {
1251:                 if(property_exists($this,$name))
1252:                     $this->$name=$record->$name;
1253:                 else
1254:                     $this->_attributes[$name]=$record->$name;
1255:             }
1256:             return true;
1257:         }
1258:         else
1259:             return false;
1260:     }
1261: 
1262:     /**
1263:      * Compares current active record with another one.
1264:      * The comparison is made by comparing table name and the primary key values of the two active records.
1265:      * @param CActiveRecord $record record to compare to
1266:      * @return boolean whether the two active records refer to the same row in the database table.
1267:      */
1268:     public function equals($record)
1269:     {
1270:         return $this->tableName()===$record->tableName() && $this->getPrimaryKey()===$record->getPrimaryKey();
1271:     }
1272: 
1273:     /**
1274:      * Returns the primary key value.
1275:      * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite.
1276:      * If primary key is not defined, null will be returned.
1277:      */
1278:     public function getPrimaryKey()
1279:     {
1280:         $table=$this->getTableSchema();
1281:         if(is_string($table->primaryKey))
1282:             return $this->{$table->primaryKey};
1283:         elseif(is_array($table->primaryKey))
1284:         {
1285:             $values=array();
1286:             foreach($table->primaryKey as $name)
1287:                 $values[$name]=$this->$name;
1288:             return $values;
1289:         }
1290:         else
1291:             return null;
1292:     }
1293: 
1294:     /**
1295:      * Sets the primary key value.
1296:      * After calling this method, the old primary key value can be obtained from {@link oldPrimaryKey}.
1297:      * @param mixed $value the new primary key value. If the primary key is composite, the new value
1298:      * should be provided as an array (column name=>column value).
1299:      * @since 1.1.0
1300:      */
1301:     public function setPrimaryKey($value)
1302:     {
1303:         $this->_pk=$this->getPrimaryKey();
1304:         $table=$this->getTableSchema();
1305:         if(is_string($table->primaryKey))
1306:             $this->{$table->primaryKey}=$value;
1307:         elseif(is_array($table->primaryKey))
1308:         {
1309:             foreach($table->primaryKey as $name)
1310:                 $this->$name=$value[$name];
1311:         }
1312:     }
1313: 
1314:     /**
1315:      * Returns the old primary key value.
1316:      * This refers to the primary key value that is populated into the record
1317:      * after executing a find method (e.g. find(), findAll()).
1318:      * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
1319:      * @return mixed the old primary key value. An array (column name=>column value) is returned if the primary key is composite.
1320:      * If primary key is not defined, null will be returned.
1321:      * @since 1.1.0
1322:      */
1323:     public function getOldPrimaryKey()
1324:     {
1325:         return $this->_pk;
1326:     }
1327: 
1328:     /**
1329:      * Sets the old primary key value.
1330:      * @param mixed $value the old primary key value.
1331:      * @since 1.1.3
1332:      */
1333:     public function setOldPrimaryKey($value)
1334:     {
1335:         $this->_pk=$value;
1336:     }
1337: 
1338:     /**
1339:      * Performs the actual DB query and populates the AR objects with the query result.
1340:      * This method is mainly internally used by other AR query methods.
1341:      * @param CDbCriteria $criteria the query criteria
1342:      * @param boolean $all whether to return all data
1343:      * @return mixed the AR objects populated with the query result
1344:      * @since 1.1.7
1345:      */
1346:     protected function query($criteria,$all=false)
1347:     {
1348:         $this->beforeFind();
1349:         $this->applyScopes($criteria);
1350: 
1351:         if(empty($criteria->with))
1352:         {
1353:             if(!$all)
1354:                 $criteria->limit=1;
1355:             $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria);
1356:             return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow());
1357:         }
1358:         else
1359:         {
1360:             $finder=$this->getActiveFinder($criteria->with);
1361:             return $finder->query($criteria,$all);
1362:         }
1363:     }
1364: 
1365:     /**
1366:      * Applies the query scopes to the given criteria.
1367:      * This method merges {@link dbCriteria} with the given criteria parameter.
1368:      * It then resets {@link dbCriteria} to be null.
1369:      * @param CDbCriteria $criteria the query criteria. This parameter may be modified by merging {@link dbCriteria}.
1370:      */
1371:     public function applyScopes(&$criteria)
1372:     {
1373:         if(!empty($criteria->scopes))
1374:         {
1375:             $scs=$this->scopes();
1376:             $c=$this->getDbCriteria();
1377:             foreach((array)$criteria->scopes as $k=>$v)
1378:             {
1379:                 if(is_integer($k))
1380:                 {
1381:                     if(is_string($v))
1382:                     {
1383:                         if(isset($scs[$v]))
1384:                         {
1385:                             $c->mergeWith($scs[$v],true);
1386:                             continue;
1387:                         }
1388:                         $scope=$v;
1389:                         $params=array();
1390:                     }
1391:                     elseif(is_array($v))
1392:                     {
1393:                         $scope=key($v);
1394:                         $params=current($v);
1395:                     }
1396:                 }
1397:                 elseif(is_string($k))
1398:                 {
1399:                     $scope=$k;
1400:                     $params=$v;
1401:                 }
1402: 
1403:                 call_user_func_array(array($this,$scope),(array)$params);
1404:             }
1405:         }
1406: 
1407:         if(isset($c) || ($c=$this->getDbCriteria(false))!==null)
1408:         {
1409:             $c->mergeWith($criteria);
1410:             $criteria=$c;
1411:             $this->resetScope(false);
1412:         }
1413:     }
1414: 
1415:     /**
1416:      * Returns the table alias to be used by the find methods.
1417:      * In relational queries, the returned table alias may vary according to
1418:      * the corresponding relation declaration. Also, the default table alias
1419:      * set by {@link setTableAlias} may be overridden by the applied scopes.
1420:      * @param boolean $quote whether to quote the alias name
1421:      * @param boolean $checkScopes whether to check if a table alias is defined in the applied scopes so far.
1422:      * This parameter must be set false when calling this method in {@link defaultScope}.
1423:      * An infinite loop would be formed otherwise.
1424:      * @return string the default table alias
1425:      * @since 1.1.1
1426:      */
1427:     public function getTableAlias($quote=false, $checkScopes=true)
1428:     {
1429:         if($checkScopes && ($criteria=$this->getDbCriteria(false))!==null && $criteria->alias!='')
1430:             $alias=$criteria->alias;
1431:         else
1432:             $alias=$this->_alias;
1433:         return $quote ? $this->getDbConnection()->getSchema()->quoteTableName($alias) : $alias;
1434:     }
1435: 
1436:     /**
1437:      * Sets the table alias to be used in queries.
1438:      * @param string $alias the table alias to be used in queries. The alias should NOT be quoted.
1439:      * @since 1.1.3
1440:      */
1441:     public function setTableAlias($alias)
1442:     {
1443:         $this->_alias=$alias;
1444:     }
1445: 
1446:     /**
1447:      * Finds a single active record with the specified condition.
1448:      * @param mixed $condition query condition or criteria.
1449:      * If a string, it is treated as query condition (the WHERE clause);
1450:      * If an array, it is treated as the initial values for constructing a {@link CDbCriteria} object;
1451:      * Otherwise, it should be an instance of {@link CDbCriteria}.
1452:      * @param array $params parameters to be bound to an SQL statement.
1453:      * This is only used when the first parameter is a string (query condition).
1454:      * In other cases, please use {@link CDbCriteria::params} to set parameters.
1455:      * @return static the record found. Null if no record is found.
1456:      */
1457:     public function find($condition='',$params=array())
1458:     {
1459:         Yii::trace(get_class($this).'.find()','system.db.ar.CActiveRecord');
1460:         $criteria=$this->getCommandBuilder()->createCriteria($condition,$params);
1461:         return $this->query($criteria);
1462:     }
1463: 
1464:     /**
1465:      * Finds all active records satisfying the specified condition.
1466:      * See {@link find()} for detailed explanation about $condition and $params.
1467:      * @param mixed $condition query condition or criteria.
1468:      * @param array $params parameters to be bound to an SQL statement.
1469:      * @return static[] list of active records satisfying the specified condition. An empty array is returned if none is found.
1470:      */
1471:     public function findAll($condition='',$params=array())
1472:     {
1473:         Yii::trace(get_class($this).'.findAll()','system.db.ar.CActiveRecord');
1474:         $criteria=$this->getCommandBuilder()->createCriteria($condition,$params);
1475:         return $this->query($criteria,true);
1476:     }
1477: 
1478:     /**
1479:      * Finds a single active record with the specified primary key.
1480:      * See {@link find()} for detailed explanation about $condition and $params.
1481:      * @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).
1482:      * @param mixed $condition query condition or criteria.
1483:      * @param array $params parameters to be bound to an SQL statement.
1484:      * @return static the record found. Null if none is found.
1485:      */
1486:     public function findByPk($pk,$condition='',$params=array())
1487:     {
1488:         Yii::trace(get_class($this).'.findByPk()','system.db.ar.CActiveRecord');
1489:         $prefix=$this->getTableAlias(true).'.';
1490:         $criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params,$prefix);
1491:         return $this->query($criteria);
1492:     }
1493: 
1494:     /**
1495:      * Finds all active records with the specified primary keys.
1496:      * See {@link find()} for detailed explanation about $condition and $params.
1497:      * @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).
1498:      * @param mixed $condition query condition or criteria.
1499:      * @param array $params parameters to be bound to an SQL statement.
1500:      * @return static[] the records found. An empty array is returned if none is found.
1501:      */
1502:     public function findAllByPk($pk,$condition='',$params=array())
1503:     {
1504:         Yii::trace(get_class($this).'.findAllByPk()','system.db.ar.CActiveRecord');
1505:         $prefix=$this->getTableAlias(true).'.';
1506:         $criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params,$prefix);
1507:         return $this->query($criteria,true);
1508:     }
1509: 
1510:     /**
1511:      * Finds a single active record that has the specified attribute values.
1512:      * See {@link find()} for detailed explanation about $condition and $params.
1513:      * @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
1514:      * An attribute value can be an array which will be used to generate an IN condition.
1515:      * @param mixed $condition query condition or criteria.
1516:      * @param array $params parameters to be bound to an SQL statement.
1517:      * @return static the record found. Null if none is found.
1518:      */
1519:     public function findByAttributes($attributes,$condition='',$params=array())
1520:     {
1521:         Yii::trace(get_class($this).'.findByAttributes()','system.db.ar.CActiveRecord');
1522:         $prefix=$this->getTableAlias(true).'.';
1523:         $criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params,$prefix);
1524:         return $this->query($criteria);
1525:     }
1526: 
1527:     /**
1528:      * Finds all active records that have the specified attribute values.
1529:      * See {@link find()} for detailed explanation about $condition and $params.
1530:      * @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
1531:      * An attribute value can be an array which will be used to generate an IN condition.
1532:      * @param mixed $condition query condition or criteria.
1533:      * @param array $params parameters to be bound to an SQL statement.
1534:      * @return static[] the records found. An empty array is returned if none is found.
1535:      */
1536:     public function findAllByAttributes($attributes,$condition='',$params=array())
1537:     {
1538:         Yii::trace(get_class($this).'.findAllByAttributes()','system.db.ar.CActiveRecord');
1539:         $prefix=$this->getTableAlias(true).'.';
1540:         $criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params,$prefix);
1541:         return $this->query($criteria,true);
1542:     }
1543: 
1544:     /**
1545:      * Finds a single active record with the specified SQL statement.
1546:      * @param string $sql the SQL statement
1547:      * @param array $params parameters to be bound to the SQL statement
1548:      * @return static the record found. Null if none is found.
1549:      */
1550:     public function findBySql($sql,$params=array())
1551:     {
1552:         Yii::trace(get_class($this).'.findBySql()','system.db.ar.CActiveRecord');
1553:         $this->beforeFind();
1554:         if(($criteria=$this->getDbCriteria(false))!==null && !empty($criteria->with))
1555:         {
1556:             $this->resetScope(false);
1557:             $finder=$this->getActiveFinder($criteria->with);
1558:             return $finder->findBySql($sql,$params);
1559:         }
1560:         else
1561:         {
1562:             $command=$this->getCommandBuilder()->createSqlCommand($sql,$params);
1563:             return $this->populateRecord($command->queryRow());
1564:         }
1565:     }
1566: 
1567:     /**
1568:      * Finds all active records using the specified SQL statement.
1569:      * @param string $sql the SQL statement
1570:      * @param array $params parameters to be bound to the SQL statement
1571:      * @return static[] the records found. An empty array is returned if none is found.
1572:      */
1573:     public function findAllBySql($sql,$params=array())
1574:     {
1575:         Yii::trace(get_class($this).'.findAllBySql()','system.db.ar.CActiveRecord');
1576:         $this->beforeFind();
1577:         if(($criteria=$this->getDbCriteria(false))!==null && !empty($criteria->with))
1578:         {
1579:             $this->resetScope(false);
1580:             $finder=$this->getActiveFinder($criteria->with);
1581:             return $finder->findAllBySql($sql,$params);
1582:         }
1583:         else
1584:         {
1585:             $command=$this->getCommandBuilder()->createSqlCommand($sql,$params);
1586:             return $this->populateRecords($command->queryAll());
1587:         }
1588:     }
1589: 
1590:     /**
1591:      * Finds the number of rows satisfying the specified query condition.
1592:      * See {@link find()} for detailed explanation about $condition and $params.
1593:      * @param mixed $condition query condition or criteria.
1594:      * @param array $params parameters to be bound to an SQL statement.
1595:      * @return string the number of rows satisfying the specified query condition. Note: type is string to keep max. precision.
1596:      */
1597:     public function count($condition='',$params=array())
1598:     {
1599:         Yii::trace(get_class($this).'.count()','system.db.ar.CActiveRecord');
1600:         $this->beforeCount();
1601:         $builder=$this->getCommandBuilder();
1602:         $criteria=$builder->createCriteria($condition,$params);
1603:         $this->applyScopes($criteria);
1604: 
1605:         if(empty($criteria->with))
1606:             return $builder->createCountCommand($this->getTableSchema(),$criteria)->queryScalar();
1607:         else
1608:         {
1609:             $finder=$this->getActiveFinder($criteria->with);
1610:             return $finder->count($criteria);
1611:         }
1612:     }
1613: 
1614:     /**
1615:      * Finds the number of rows that have the specified attribute values.
1616:      * See {@link find()} for detailed explanation about $condition and $params.
1617:      * @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
1618:      * An attribute value can be an array which will be used to generate an IN condition.
1619:      * @param mixed $condition query condition or criteria.
1620:      * @param array $params parameters to be bound to an SQL statement.
1621:      * @return string the number of rows satisfying the specified query condition. Note: type is string to keep max. precision.
1622:      * @since 1.1.4
1623:      */
1624:     public function countByAttributes($attributes,$condition='',$params=array())
1625:     {
1626:         Yii::trace(get_class($this).'.countByAttributes()','system.db.ar.CActiveRecord');
1627:         $prefix=$this->getTableAlias(true).'.';
1628:         $builder=$this->getCommandBuilder();
1629:         $this->beforeCount();
1630:         $criteria=$builder->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params,$prefix);
1631:         $this->applyScopes($criteria);
1632: 
1633:         if(empty($criteria->with))
1634:             return $builder->createCountCommand($this->getTableSchema(),$criteria)->queryScalar();
1635:         else
1636:         {
1637:             $finder=$this->getActiveFinder($criteria->with);
1638:             return $finder->count($criteria);
1639:         }
1640:     }
1641: 
1642:     /**
1643:      * Finds the number of rows using the given SQL statement.
1644:      * This is equivalent to calling {@link CDbCommand::queryScalar} with the specified
1645:      * SQL statement and the parameters.
1646:      * @param string $sql the SQL statement
1647:      * @param array $params parameters to be bound to the SQL statement
1648:      * @return string the number of rows using the given SQL statement. Note: type is string to keep max. precision.
1649:      */
1650:     public function countBySql($sql,$params=array())
1651:     {
1652:         Yii::trace(get_class($this).'.countBySql()','system.db.ar.CActiveRecord');
1653:         $this->beforeCount();
1654:         return $this->getCommandBuilder()->createSqlCommand($sql,$params)->queryScalar();
1655:     }
1656: 
1657:     /**
1658:      * Checks whether there is row satisfying the specified condition.
1659:      * See {@link find()} for detailed explanation about $condition and $params.
1660:      * @param mixed $condition query condition or criteria.
1661:      * @param array $params parameters to be bound to an SQL statement.
1662:      * @return boolean whether there is row satisfying the specified condition.
1663:      */
1664:     public function exists($condition='',$params=array())
1665:     {
1666:         Yii::trace(get_class($this).'.exists()','system.db.ar.CActiveRecord');
1667:         $builder=$this->getCommandBuilder();
1668:         $criteria=$builder->createCriteria($condition,$params);
1669:         $table=$this->getTableSchema();
1670:         $criteria->select='1';
1671:         $criteria->limit=1;
1672:         $this->applyScopes($criteria);
1673: 
1674:         if(empty($criteria->with))
1675:             return $builder->createFindCommand($table,$criteria,$this->getTableAlias(false, false))->queryRow()!==false;
1676:         else
1677:         {
1678:             $criteria->select='*';
1679:             $finder=$this->getActiveFinder($criteria->with);
1680:             return $finder->count($criteria)>0;
1681:         }
1682:     }
1683: 
1684:     /**
1685:      * Specifies which related objects should be eagerly loaded.
1686:      * This method takes variable number of parameters. Each parameter specifies
1687:      * the name of a relation or child-relation. For example,
1688:      * <pre>
1689:      * // find all posts together with their author and comments
1690:      * Post::model()->with('author','comments')->findAll();
1691:      * // find all posts together with their author and the author's profile
1692:      * Post::model()->with('author','author.profile')->findAll();
1693:      * </pre>
1694:      * The relations should be declared in {@link relations()}.
1695:      *
1696:      * By default, the options specified in {@link relations()} will be used
1697:      * to do relational query. In order to customize the options on the fly,
1698:      * we should pass an array parameter to the with() method. The array keys
1699:      * are relation names, and the array values are the corresponding query options.
1700:      * For example,
1701:      * <pre>
1702:      * Post::model()->with(array(
1703:      *     'author'=>array('select'=>'id, name'),
1704:      *     'comments'=>array('condition'=>'approved=1', 'order'=>'create_time'),
1705:      * ))->findAll();
1706:      * </pre>
1707:      *
1708:      * @return static the AR object itself.
1709:      */
1710:     public function with()
1711:     {
1712:         if(func_num_args()>0)
1713:         {
1714:             $with=func_get_args();
1715:             if(is_array($with[0]))  // the parameter is given as an array
1716:                 $with=$with[0];
1717:             if(!empty($with))
1718:                 $this->getDbCriteria()->mergeWith(array('with'=>$with));
1719:         }
1720:         return $this;
1721:     }
1722: 
1723:     /**
1724:      * Sets {@link CDbCriteria::together} property to be true.
1725:      * This is only used in relational AR query. Please refer to {@link CDbCriteria::together}
1726:      * for more details.
1727:      * @return static the AR object itself
1728:      * @since 1.1.4
1729:      */
1730:     public function together()
1731:     {
1732:         $this->getDbCriteria()->together=true;
1733:         return $this;
1734:     }
1735: 
1736:     /**
1737:      * Updates records with the specified primary key(s).
1738:      * See {@link find()} for detailed explanation about $condition and $params.
1739:      * Note, the attributes are not checked for safety and validation is NOT performed.
1740:      * @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).
1741:      * @param array $attributes list of attributes (name=>$value) to be updated
1742:      * @param mixed $condition query condition or criteria.
1743:      * @param array $params parameters to be bound to an SQL statement.
1744:      * @return integer the number of rows being updated
1745:      */
1746:     public function updateByPk($pk,$attributes,$condition='',$params=array())
1747:     {
1748:         Yii::trace(get_class($this).'.updateByPk()','system.db.ar.CActiveRecord');
1749:         $builder=$this->getCommandBuilder();
1750:         $table=$this->getTableSchema();
1751:         $criteria=$builder->createPkCriteria($table,$pk,$condition,$params);
1752:         $command=$builder->createUpdateCommand($table,$attributes,$criteria);
1753:         return $command->execute();
1754:     }
1755: 
1756:     /**
1757:      * Updates records with the specified condition.
1758:      * See {@link find()} for detailed explanation about $condition and $params.
1759:      * Note, the attributes are not checked for safety and no validation is done.
1760:      * @param array $attributes list of attributes (name=>$value) to be updated
1761:      * @param mixed $condition query condition or criteria.
1762:      * @param array $params parameters to be bound to an SQL statement.
1763:      * @return integer the number of rows being updated
1764:      */
1765:     public function updateAll($attributes,$condition='',$params=array())
1766:     {
1767:         Yii::trace(get_class($this).'.updateAll()','system.db.ar.CActiveRecord');
1768:         $builder=$this->getCommandBuilder();
1769:         $criteria=$builder->createCriteria($condition,$params);
1770:         $command=$builder->createUpdateCommand($this->getTableSchema(),$attributes,$criteria);
1771:         return $command->execute();
1772:     }
1773: 
1774:     /**
1775:      * Updates one or several counter columns.
1776:      * Note, this updates all rows of data unless a condition or criteria is specified.
1777:      * See {@link find()} for detailed explanation about $condition and $params.
1778:      * @param array $counters the counters to be updated (column name=>increment value)
1779:      * @param mixed $condition query condition or criteria.
1780:      * @param array $params parameters to be bound to an SQL statement.
1781:      * @return integer the number of rows being updated
1782:      * @see saveCounters
1783:      */
1784:     public function updateCounters($counters,$condition='',$params=array())
1785:     {
1786:         Yii::trace(get_class($this).'.updateCounters()','system.db.ar.CActiveRecord');
1787:         $builder=$this->getCommandBuilder();
1788:         $criteria=$builder->createCriteria($condition,$params);
1789:         $command=$builder->createUpdateCounterCommand($this->getTableSchema(),$counters,$criteria);
1790:         return $command->execute();
1791:     }
1792: 
1793:     /**
1794:      * Deletes rows with the specified primary key.
1795:      * See {@link find()} for detailed explanation about $condition and $params.
1796:      * @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).
1797:      * @param mixed $condition query condition or criteria.
1798:      * @param array $params parameters to be bound to an SQL statement.
1799:      * @return integer the number of rows deleted
1800:      */
1801:     public function deleteByPk($pk,$condition='',$params=array())
1802:     {
1803:         Yii::trace(get_class($this).'.deleteByPk()','system.db.ar.CActiveRecord');
1804:         $builder=$this->getCommandBuilder();
1805:         $criteria=$builder->createPkCriteria($this->getTableSchema(),$pk,$condition,$params);
1806:         $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria);
1807:         return $command->execute();
1808:     }
1809: 
1810:     /**
1811:      * Deletes rows with the specified condition.
1812:      * See {@link find()} for detailed explanation about $condition and $params.
1813:      * @param mixed $condition query condition or criteria.
1814:      * @param array $params parameters to be bound to an SQL statement.
1815:      * @return integer the number of rows deleted
1816:      */
1817:     public function deleteAll($condition='',$params=array())
1818:     {
1819:         Yii::trace(get_class($this).'.deleteAll()','system.db.ar.CActiveRecord');
1820:         $builder=$this->getCommandBuilder();
1821:         $criteria=$builder->createCriteria($condition,$params);
1822:         $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria);
1823:         return $command->execute();
1824:     }
1825: 
1826:     /**
1827:      * Deletes rows which match the specified attribute values.
1828:      * See {@link find()} for detailed explanation about $condition and $params.
1829:      * @param array $attributes list of attribute values (indexed by attribute names) that the active records should match.
1830:      * An attribute value can be an array which will be used to generate an IN condition.
1831:      * @param mixed $condition query condition or criteria.
1832:      * @param array $params parameters to be bound to an SQL statement.
1833:      * @return integer number of rows affected by the execution.
1834:      */
1835:     public function deleteAllByAttributes($attributes,$condition='',$params=array())
1836:     {
1837:         Yii::trace(get_class($this).'.deleteAllByAttributes()','system.db.ar.CActiveRecord');
1838:         $builder=$this->getCommandBuilder();
1839:         $table=$this->getTableSchema();
1840:         $criteria=$builder->createColumnCriteria($table,$attributes,$condition,$params);
1841:         $command=$builder->createDeleteCommand($table,$criteria);
1842:         return $command->execute();
1843:     }
1844: 
1845:     /**
1846:      * Creates an active record with the given attributes.
1847:      * This method is internally used by the find methods.
1848:      * @param array $attributes attribute values (column name=>column value)
1849:      * @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated.
1850:      * @return static the newly created active record. The class of the object is the same as the model class.
1851:      * Null is returned if the input data is false.
1852:      */
1853:     public function populateRecord($attributes,$callAfterFind=true)
1854:     {
1855:         if($attributes!==false)
1856:         {
1857:             $record=$this->instantiate($attributes);
1858:             $record->setScenario('update');
1859:             $record->init();
1860:             $md=$record->getMetaData();
1861:             foreach($attributes as $name=>$value)
1862:             {
1863:                 if(property_exists($record,$name))
1864:                     $record->$name=$value;
1865:                 elseif(isset($md->columns[$name]))
1866:                     $record->_attributes[$name]=$value;
1867:             }
1868:             $record->_pk=$record->getPrimaryKey();
1869:             $record->attachBehaviors($record->behaviors());
1870:             if($callAfterFind)
1871:                 $record->afterFind();
1872:             return $record;
1873:         }
1874:         else
1875:             return null;
1876:     }
1877: 
1878:     /**
1879:      * Creates a list of active records based on the input data.
1880:      * This method is internally used by the find methods.
1881:      * @param array $data list of attribute values for the active records.
1882:      * @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated.
1883:      * @param string $index the name of the attribute whose value will be used as indexes of the query result array.
1884:      * If null, it means the array will be indexed by zero-based integers.
1885:      * @return static[] list of active records.
1886:      */
1887:     public function populateRecords($data,$callAfterFind=true,$index=null)
1888:     {
1889:         $records=array();
1890:         foreach($data as $attributes)
1891:         {
1892:             if(($record=$this->populateRecord($attributes,$callAfterFind))!==null)
1893:             {
1894:                 if($index===null)
1895:                     $records[]=$record;
1896:                 else
1897:                     $records[$record->$index]=$record;
1898:             }
1899:         }
1900:         return $records;
1901:     }
1902: 
1903:     /**
1904:      * Creates an active record instance.
1905:      * This method is called by {@link populateRecord} and {@link populateRecords}.
1906:      * You may override this method if the instance being created
1907:      * depends the attributes that are to be populated to the record.
1908:      * For example, by creating a record based on the value of a column,
1909:      * you may implement the so-called single-table inheritance mapping.
1910:      * @param array $attributes list of attribute values for the active records.
1911:      * @return static the active record
1912:      */
1913:     protected function instantiate($attributes)
1914:     {
1915:         $class=get_class($this);
1916:         $model=new $class(null);
1917:         return $model;
1918:     }
1919: 
1920:     /**
1921:      * Returns whether there is an element at the specified offset.
1922:      * This method is required by the interface ArrayAccess.
1923:      * @param mixed $offset the offset to check on
1924:      * @return boolean
1925:      */
1926:     public function offsetExists($offset)
1927:     {
1928:         return $this->__isset($offset);
1929:     }
1930: }
1931: 
1932: 
1933: /**
1934:  * CBaseActiveRelation is the base class for all active relations.
1935:  * @author Qiang Xue <qiang.xue@gmail.com>
1936:  * @package system.db.ar
1937:  */
1938: class CBaseActiveRelation extends CComponent
1939: {
1940:     /**
1941:      * @var string name of the related object
1942:      */
1943:     public $name;
1944:     /**
1945:      * @var string name of the related active record class
1946:      */
1947:     public $className;
1948:     /**
1949:      * @var mixed the foreign key in this relation
1950:      */
1951:     public $foreignKey;
1952:     /**
1953:      * @var mixed list of column names (an array, or a string of names separated by commas) to be selected.
1954:      * Do not quote or prefix the column names unless they are used in an expression.
1955:      * In that case, you should prefix the column names with 'relationName.'.
1956:      */
1957:     public $select='*';
1958:     /**
1959:      * @var string WHERE clause. For {@link CActiveRelation} descendant classes, column names
1960:      * referenced in the condition should be disambiguated with prefix 'relationName.'.
1961:      */
1962:     public $condition='';
1963:     /**
1964:      * @var array the parameters that are to be bound to the condition.
1965:      * The keys are parameter placeholder names, and the values are parameter values.
1966:      */
1967:     public $params=array();
1968:     /**
1969:      * @var string GROUP BY clause. For {@link CActiveRelation} descendant classes, column names
1970:      * referenced in this property should be disambiguated with prefix 'relationName.'.
1971:      */
1972:     public $group='';
1973:     /**
1974:      * @var string how to join with other tables. This refers to the JOIN clause in an SQL statement.
1975:      * For example, <code>'LEFT JOIN users ON users.id=authorID'</code>.
1976:      * @since 1.1.3
1977:      */
1978:     public $join='';
1979:     /**
1980:      * @var string|array property for setting post-JOIN operations such as USE INDEX.
1981:      * String typed value can be used with JOINs for HAS_MANY and MANY_MANY relations, while array typed
1982:      * value designed to be used only with MANY_MANY relations. First array element will be used for junction
1983:      * table JOIN and second array element will be used for target table JOIN.
1984:      * @since 1.1.16
1985:      */
1986:     public $joinOptions='';
1987:     /**
1988:      * @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names
1989:      * referenced in this property should be disambiguated with prefix 'relationName.'.
1990:      */
1991:     public $having='';
1992:     /**
1993:      * @var string ORDER BY clause. For {@link CActiveRelation} descendant classes, column names
1994:      * referenced in this property should be disambiguated with prefix 'relationName.'.
1995:      */
1996:     public $order='';
1997: 
1998:     /**
1999:      * Constructor.
2000:      * @param string $name name of the relation
2001:      * @param string $className name of the related active record class
2002:      * @param string $foreignKey foreign key for this relation
2003:      * @param array $options additional options (name=>value). The keys must be the property names of this class.
2004:      */
2005:     public function __construct($name,$className,$foreignKey,$options=array())
2006:     {
2007:         $this->name=$name;
2008:         $this->className=$className;
2009:         $this->foreignKey=$foreignKey;
2010:         foreach($options as $name=>$value)
2011:             $this->$name=$value;
2012:     }
2013: 
2014:     /**
2015:      * Merges this relation with a criteria specified dynamically.
2016:      * @param array $criteria the dynamically specified criteria
2017:      * @param boolean $fromScope whether the criteria to be merged is from scopes
2018:      */
2019:     public function mergeWith($criteria,$fromScope=false)
2020:     {
2021:         if($criteria instanceof CDbCriteria)
2022:             $criteria=$criteria->toArray();
2023:         if(isset($criteria['select']) && $this->select!==$criteria['select'])
2024:         {
2025:             if($this->select==='*')
2026:                 $this->select=$criteria['select'];
2027:             elseif($criteria['select']!=='*')
2028:             {
2029:                 $select1=is_string($this->select)?preg_split('/\s*,\s*/',trim($this->select),-1,PREG_SPLIT_NO_EMPTY):$this->select;
2030:                 $select2=is_string($criteria['select'])?preg_split('/\s*,\s*/',trim($criteria['select']),-1,PREG_SPLIT_NO_EMPTY):$criteria['select'];
2031:                 $this->select=array_merge($select1,array_diff($select2,$select1));
2032:             }
2033:         }
2034: 
2035:         if(isset($criteria['condition']) && $this->condition!==$criteria['condition'])
2036:         {
2037:             if($this->condition==='')
2038:                 $this->condition=$criteria['condition'];
2039:             elseif($criteria['condition']!=='')
2040:                 $this->condition="({$this->condition}) AND ({$criteria['condition']})";
2041:         }
2042: 
2043:         if(isset($criteria['params']) && $this->params!==$criteria['params'])
2044:             $this->params=array_merge($this->params,$criteria['params']);
2045: 
2046:         if(isset($criteria['order']) && $this->order!==$criteria['order'])
2047:         {
2048:             if($this->order==='')
2049:                 $this->order=$criteria['order'];
2050:             elseif($criteria['order']!=='')
2051:                 $this->order=$criteria['order'].', '.$this->order;
2052:         }
2053: 
2054:         if(isset($criteria['group']) && $this->group!==$criteria['group'])
2055:         {
2056:             if($this->group==='')
2057:                 $this->group=$criteria['group'];
2058:             elseif($criteria['group']!=='')
2059:                 $this->group.=', '.$criteria['group'];
2060:         }
2061: 
2062:         if(isset($criteria['join']) && $this->join!==$criteria['join'])
2063:         {
2064:             if($this->join==='')
2065:                 $this->join=$criteria['join'];
2066:             elseif($criteria['join']!=='')
2067:                 $this->join.=' '.$criteria['join'];
2068:         }
2069: 
2070:         if(isset($criteria['having']) && $this->having!==$criteria['having'])
2071:         {
2072:             if($this->having==='')
2073:                 $this->having=$criteria['having'];
2074:             elseif($criteria['having']!=='')
2075:                 $this->having="({$this->having}) AND ({$criteria['having']})";
2076:         }
2077:     }
2078: }
2079: 
2080: 
2081: /**
2082:  * CStatRelation represents a statistical relational query.
2083:  * @author Qiang Xue <qiang.xue@gmail.com>
2084:  * @package system.db.ar
2085:  */
2086: class CStatRelation extends CBaseActiveRelation
2087: {
2088:     /**
2089:      * @var string the statistical expression. Defaults to 'COUNT(*)', meaning
2090:      * the count of child objects.
2091:      */
2092:     public $select='COUNT(*)';
2093:     /**
2094:      * @var mixed the default value to be assigned to those records that do not
2095:      * receive a statistical query result. Defaults to 0.
2096:      */
2097:     public $defaultValue=0;
2098:     /**
2099:      * @var mixed scopes to apply
2100:      * Can be set to the one of the following:
2101:      * <ul>
2102:      * <li>Single scope: 'scopes'=>'scopeName'.</li>
2103:      * <li>Multiple scopes: 'scopes'=>array('scopeName1','scopeName2').</li>
2104:      * </ul>
2105:      * @since 1.1.16
2106:      */
2107:     public $scopes;
2108: 
2109:     /**
2110:      * Merges this relation with a criteria specified dynamically.
2111:      * @param array $criteria the dynamically specified criteria
2112:      * @param boolean $fromScope whether the criteria to be merged is from scopes
2113:      */
2114:     public function mergeWith($criteria,$fromScope=false)
2115:     {
2116:         if($criteria instanceof CDbCriteria)
2117:             $criteria=$criteria->toArray();
2118:         parent::mergeWith($criteria,$fromScope);
2119: 
2120:         if(isset($criteria['defaultValue']))
2121:             $this->defaultValue=$criteria['defaultValue'];
2122:     }
2123: }
2124: 
2125: 
2126: /**
2127:  * CActiveRelation is the base class for representing active relations that bring back related objects.
2128:  * @author Qiang Xue <qiang.xue@gmail.com>
2129:  * @package system.db.ar
2130:  * @since 1.0
2131:  */
2132: class CActiveRelation extends CBaseActiveRelation
2133: {
2134:     /**
2135:      * @var string join type. Defaults to 'LEFT OUTER JOIN'.
2136:      */
2137:     public $joinType='LEFT OUTER JOIN';
2138:     /**
2139:      * @var string ON clause. The condition specified here will be appended to the joining condition using AND operator.
2140:      */
2141:     public $on='';
2142:     /**
2143:      * @var string the alias for the table that this relation refers to. Defaults to null, meaning
2144:      * the alias will be the same as the relation name.
2145:      */
2146:     public $alias;
2147:     /**
2148:      * @var string|array specifies which related objects should be eagerly loaded when this related object is lazily loaded.
2149:      * For more details about this property, see {@link CActiveRecord::with()}.
2150:      */
2151:     public $with=array();
2152:     /**
2153:      * @var boolean whether this table should be joined with the primary table.
2154:      * When setting this property to be false, the table associated with this relation will
2155:      * appear in a separate JOIN statement.
2156:      * If this property is set true, then the corresponding table will ALWAYS be joined together
2157:      * with the primary table, no matter the primary table is limited or not.
2158:      * If this property is not set, the corresponding table will be joined with the primary table
2159:      * only when the primary table is not limited.
2160:      */
2161:     public $together;
2162:     /**
2163:      * @var mixed scopes to apply
2164:      * Can be set to the one of the following:
2165:      * <ul>
2166:      * <li>Single scope: 'scopes'=>'scopeName'.</li>
2167:      * <li>Multiple scopes: 'scopes'=>array('scopeName1','scopeName2').</li>
2168:      * </ul>
2169:      * @since 1.1.9
2170:      */
2171:      public $scopes;
2172:     /**
2173:      * @var string the name of the relation that should be used as the bridge to this relation.
2174:      * Defaults to null, meaning don't use any bridge.
2175:      * @since 1.1.7
2176:      */
2177:     public $through;
2178: 
2179:     /**
2180:      * Merges this relation with a criteria specified dynamically.
2181:      * @param array $criteria the dynamically specified criteria
2182:      * @param boolean $fromScope whether the criteria to be merged is from scopes
2183:      */
2184:     public function mergeWith($criteria,$fromScope=false)
2185:     {
2186:         if($criteria instanceof CDbCriteria)
2187:             $criteria=$criteria->toArray();
2188:         if($fromScope)
2189:         {
2190:             if(isset($criteria['condition']) && $this->on!==$criteria['condition'])
2191:             {
2192:                 if($this->on==='')
2193:                     $this->on=$criteria['condition'];
2194:                 elseif($criteria['condition']!=='')
2195:                     $this->on="({$this->on}) AND ({$criteria['condition']})";
2196:             }
2197:             unset($criteria['condition']);
2198:         }
2199: 
2200:         parent::mergeWith($criteria);
2201: 
2202:         if(isset($criteria['joinType']))
2203:             $this->joinType=$criteria['joinType'];
2204: 
2205:         if(isset($criteria['on']) && $this->on!==$criteria['on'])
2206:         {
2207:             if($this->on==='')
2208:                 $this->on=$criteria['on'];
2209:             elseif($criteria['on']!=='')
2210:                 $this->on="({$this->on}) AND ({$criteria['on']})";
2211:         }
2212: 
2213:         if(isset($criteria['with']))
2214:             $this->with=$criteria['with'];
2215: 
2216:         if(isset($criteria['alias']))
2217:             $this->alias=$criteria['alias'];
2218: 
2219:         if(isset($criteria['together']))
2220:             $this->together=$criteria['together'];
2221:     }
2222: }
2223: 
2224: 
2225: /**
2226:  * CBelongsToRelation represents the parameters specifying a BELONGS_TO relation.
2227:  * @author Qiang Xue <qiang.xue@gmail.com>
2228:  * @package system.db.ar
2229:  * @since 1.0
2230:  */
2231: class CBelongsToRelation extends CActiveRelation
2232: {
2233: }
2234: 
2235: 
2236: /**
2237:  * CHasOneRelation represents the parameters specifying a HAS_ONE relation.
2238:  * @author Qiang Xue <qiang.xue@gmail.com>
2239:  * @package system.db.ar
2240:  * @since 1.0
2241:  */
2242: class CHasOneRelation extends CActiveRelation
2243: {
2244: }
2245: 
2246: 
2247: /**
2248:  * CHasManyRelation represents the parameters specifying a HAS_MANY relation.
2249:  * @author Qiang Xue <qiang.xue@gmail.com>
2250:  * @package system.db.ar
2251:  * @since 1.0
2252:  */
2253: class CHasManyRelation extends CActiveRelation
2254: {
2255:     /**
2256:      * @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.
2257:      */
2258:     public $limit=-1;
2259:     /**
2260:      * @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.
2261:      */
2262:     public $offset=-1;
2263:     /**
2264:      * @var string the name of the column that should be used as the key for storing related objects.
2265:      * Defaults to null, meaning using zero-based integer IDs.
2266:      */
2267:     public $index;
2268: 
2269:     /**
2270:      * Merges this relation with a criteria specified dynamically.
2271:      * @param array $criteria the dynamically specified criteria
2272:      * @param boolean $fromScope whether the criteria to be merged is from scopes
2273:      */
2274:     public function mergeWith($criteria,$fromScope=false)
2275:     {
2276:         if($criteria instanceof CDbCriteria)
2277:             $criteria=$criteria->toArray();
2278:         parent::mergeWith($criteria,$fromScope);
2279:         if(isset($criteria['limit']) && $criteria['limit']>0)
2280:             $this->limit=$criteria['limit'];
2281: 
2282:         if(isset($criteria['offset']) && $criteria['offset']>=0)
2283:             $this->offset=$criteria['offset'];
2284: 
2285:         if(isset($criteria['index']))
2286:             $this->index=$criteria['index'];
2287:     }
2288: }
2289: 
2290: 
2291: /**
2292:  * CManyManyRelation represents the parameters specifying a MANY_MANY relation.
2293:  * @author Qiang Xue <qiang.xue@gmail.com>
2294:  * @package system.db.ar
2295:  * @since 1.0
2296:  */
2297: class CManyManyRelation extends CHasManyRelation
2298: {
2299:     /**
2300:      * @var string name of the junction table for the many-to-many relation.
2301:      */
2302:     private $_junctionTableName=null;
2303:     /**
2304:      * @var array list of foreign keys of the junction table for the many-to-many relation.
2305:      */
2306:     private $_junctionForeignKeys=null;
2307: 
2308:     /**
2309:      * @return string junction table name.
2310:      * @since 1.1.12
2311:      */
2312:     public function getJunctionTableName()
2313:     {
2314:         if ($this->_junctionTableName===null)
2315:             $this->initJunctionData();
2316:         return $this->_junctionTableName;
2317:     }
2318: 
2319:     /**
2320:      * @return array list of junction table foreign keys.
2321:      * @since 1.1.12
2322:      */
2323:     public function getJunctionForeignKeys()
2324:     {
2325:         if ($this->_junctionForeignKeys===null)
2326:             $this->initJunctionData();
2327:         return $this->_junctionForeignKeys;
2328:     }
2329: 
2330:     /**
2331:      * Initializes values of {@link junctionTableName} and {@link junctionForeignKeys} parsing
2332:      * {@link foreignKey} value.
2333:      * @throws CDbException if {@link foreignKey} has been specified in wrong format.
2334:      */
2335:     private function initJunctionData()
2336:     {
2337:         if(!preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->foreignKey,$matches))
2338:             throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".',
2339:                 array('{class}'=>$this->className,'{relation}'=>$this->name)));
2340:         $this->_junctionTableName=$matches[1];
2341:         $this->_junctionForeignKeys=preg_split('/\s*,\s*/',$matches[2],-1,PREG_SPLIT_NO_EMPTY);
2342:     }
2343: }
2344: 
2345: 
2346: /**
2347:  * CActiveRecordMetaData represents the meta-data for an Active Record class.
2348:  *
2349:  * @author Qiang Xue <qiang.xue@gmail.com>
2350:  * @package system.db.ar
2351:  * @since 1.0
2352:  */
2353: class CActiveRecordMetaData
2354: {
2355:     /**
2356:      * @var CDbTableSchema the table schema information
2357:      */
2358:     public $tableSchema;
2359:     /**
2360:      * @var array table columns
2361:      */
2362:     public $columns;
2363:     /**
2364:      * @var array list of relations
2365:      */
2366:     public $relations=array();
2367:     /**
2368:      * @var array attribute default values
2369:      */
2370:     public $attributeDefaults=array();
2371: 
2372:     private $_modelClassName;
2373: 
2374:     /**
2375:      * Constructor.
2376:      * @param CActiveRecord $model the model instance
2377:      * @throws CDbException if specified table for active record class cannot be found in the database
2378:      */
2379:     public function __construct($model)
2380:     {
2381:         $this->_modelClassName=get_class($model);
2382: 
2383:         $tableName=$model->tableName();
2384:         if(($table=$model->getDbConnection()->getSchema()->getTable($tableName))===null)
2385:             throw new CDbException(Yii::t('yii','The table "{table}" for active record class "{class}" cannot be found in the database.',
2386:                 array('{class}'=>$this->_modelClassName,'{table}'=>$tableName)));
2387:                 
2388:         if(($modelPk=$model->primaryKey())!==null || $table->primaryKey===null)
2389:         {
2390:             $table->primaryKey=$modelPk;
2391:             if(is_string($table->primaryKey) && isset($table->columns[$table->primaryKey]))
2392:                 $table->columns[$table->primaryKey]->isPrimaryKey=true;
2393:             elseif(is_array($table->primaryKey))
2394:             {
2395:                 foreach($table->primaryKey as $name)
2396:                 {
2397:                     if(isset($table->columns[$name]))
2398:                         $table->columns[$name]->isPrimaryKey=true;
2399:                 }
2400:             }
2401:         }
2402:         $this->tableSchema=$table;
2403:         $this->columns=$table->columns;
2404: 
2405:         foreach($table->columns as $name=>$column)
2406:         {
2407:             if(!$column->isPrimaryKey && $column->defaultValue!==null)
2408:                 $this->attributeDefaults[$name]=$column->defaultValue;
2409:         }
2410: 
2411:         foreach($model->relations() as $name=>$config)
2412:         {
2413:             $this->addRelation($name,$config);
2414:         }
2415:     }
2416: 
2417:     /**
2418:      * Adds a relation.
2419:      *
2420:      * $config is an array with three elements:
2421:      * relation type, the related active record class and the foreign key.
2422:      *
2423:      * @throws CDbException
2424:      * @param string $name $name Name of the relation.
2425:      * @param array $config $config Relation parameters.
2426:      * @return void
2427:      * @since 1.1.2
2428:      */
2429:     public function addRelation($name,$config)
2430:     {
2431:         if(isset($config[0],$config[1],$config[2]))  // relation class, AR class, FK
2432:             $this->relations[$name]=new $config[0]($name,$config[1],$config[2],array_slice($config,3));
2433:         else
2434:             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}'=>$this->_modelClassName,'{relation}'=>$name)));
2435:     }
2436: 
2437:     /**
2438:      * Checks if there is a relation with specified name defined.
2439:      *
2440:      * @param string $name $name Name of the relation.
2441:      * @return boolean
2442:      * @since 1.1.2
2443:      */
2444:     public function hasRelation($name)
2445:     {
2446:         return isset($this->relations[$name]);
2447:     }
2448: 
2449:     /**
2450:      * Deletes a relation with specified name.
2451:      *
2452:      * @param string $name $name
2453:      * @return void
2454:      * @since 1.1.2
2455:      */
2456:     public function removeRelation($name)
2457:     {
2458:         unset($this->relations[$name]);
2459:     }
2460: }
2461: 
API documentation generated by ApiGen 2.8.0