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>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: