From d4b19712c271c3bf9d16909768c4bd84d617afd5 Mon Sep 17 00:00:00 2001 From: "ctrlaltca@gmail.com" <> Date: Mon, 20 Jun 2011 14:26:39 +0000 Subject: killed the experimental activecontrols implementation backported from yii --- .../DEPRECATED_COMPATIBILITY_REASONS_ONLY | 3 - .../Relations/TActiveRecordRelationContext.php | 230 --- .../Testing/Data/ActiveRecord/TActiveFinder.php | 1279 -------------- .../Testing/Data/ActiveRecord/TActiveRecord.php | 1843 -------------------- .../Data/ActiveRecord/TActiveRecordBehavior.php | 94 - .../Data/ActiveRecord/TActiveRecordCriteria.php | 254 --- 6 files changed, 3703 deletions(-) delete mode 100644 framework/Testing/Data/ActiveRecord/Relations/DEPRECATED_COMPATIBILITY_REASONS_ONLY delete mode 100644 framework/Testing/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php delete mode 100644 framework/Testing/Data/ActiveRecord/TActiveFinder.php delete mode 100644 framework/Testing/Data/ActiveRecord/TActiveRecord.php delete mode 100644 framework/Testing/Data/ActiveRecord/TActiveRecordBehavior.php delete mode 100644 framework/Testing/Data/ActiveRecord/TActiveRecordCriteria.php (limited to 'framework/Testing/Data/ActiveRecord') diff --git a/framework/Testing/Data/ActiveRecord/Relations/DEPRECATED_COMPATIBILITY_REASONS_ONLY b/framework/Testing/Data/ActiveRecord/Relations/DEPRECATED_COMPATIBILITY_REASONS_ONLY deleted file mode 100644 index 8c7a4073..00000000 --- a/framework/Testing/Data/ActiveRecord/Relations/DEPRECATED_COMPATIBILITY_REASONS_ONLY +++ /dev/null @@ -1,3 +0,0 @@ -Please note that this file is used for old Prado ActiveRecord applications which use $record->withAnotherRecord - syntax. - -You shouln't use that syntax anymore as it's deprecated in this portation of Yii ActiveRecords. diff --git a/framework/Testing/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Testing/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php deleted file mode 100644 index f13043df..00000000 --- a/framework/Testing/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php +++ /dev/null @@ -1,230 +0,0 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2008 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.Testing.Data.ActiveRecord.Relations - */ - -/** - * TActiveRecordRelationContext holds information regarding record relationships - * such as record relation property name, query criteria and foreign object record - * class names. - * - * This class is use internally by passing a context to the TActiveRecordRelation - * constructor. - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Testing.Data.ActiveRecord.Relations - * @since 3.1 - */ -class TActiveRecordRelationContext -{ - private $_property; - private $_record; - private $_relation; //data from an entry of TActiveRecord::$RELATION - private $_fkeys; - - public function __construct($record, $property=null, $relation=null) - { - $this->_record=$record; - $this->_property=$property; - $this->_relation=$relation; - } - - /** - * @return boolean true if the relation is defined in TActiveRecord::$RELATIONS - * @since 3.1.2 - */ - public function hasRecordRelation() - { - return $this->_relation!==null; - } - - public function getPropertyValue() - { - $obj = $this->getSourceRecord(); - return $obj->getColumnValue($this->getProperty()); - } - - /** - * @return string name of the record property that the relationship results will be assigned to. - */ - public function getProperty() - { - return $this->_property; - } - - /** - * @return TActiveRecord the active record instance that queried for its related records. - */ - public function getSourceRecord() - { - return $this->_record; - } - - /** - * @return array foreign key of this relations, the keys is dependent on the - * relationship type. - * @since 3.1.2 - */ - public function getRelationForeignKeys() - { - if($this->_fkeys===null) - $this->_fkeys=$this->getRelationHandler()->getRelationForeignKeys(); - return $this->_fkeys; - } - - /** - * @return string HAS_MANY, HAS_ONE, or BELONGS_TO - */ - public function getRelationType() - { - return $this->_relation[0]; - } - - /** - * @return string foreign record class name. - */ - public function getForeignRecordClass() - { - return $this->_relation[1]; - } - - /** - * @return string foreign key field names, comma delimited. - * @since 3.1.2 - */ - public function getFkField() - { - return $this->_relation[2]; - } - - /** - * @return string the query condition for the relation as specified in RELATIONS - * @since 3.1.2 - */ - public function getCondition() - { - return isset($this->_relation[3])?$this->_relation[3]:null; - } - - /** - * @return array the query parameters for the relation as specified in RELATIONS - * @since 3.1.2 - */ - public function getParameters() - { - return isset($this->_relation[4])?$this->_relation[4]:array(); - } - - /** - * @return boolean true if the 3rd element of an TActiveRecord::$RELATION entry is set. - * @since 3.1.2 - */ - public function hasFkField() - { - $notManyToMany = $this->getRelationType() !== TActiveRecord::MANY_TO_MANY; - return $notManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]); - } - - /** - * @return string the M-N relationship association table name. - */ - public function getAssociationTable() - { - return $this->_relation[2]; - } - - /** - * @return boolean true if the relationship is HAS_MANY and requires an association table. - */ - public function hasAssociationTable() - { - $isManyToMany = $this->getRelationType() === TActiveRecord::MANY_TO_MANY; - return $isManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]); - } - - /** - * @return TActiveRecord corresponding relationship foreign object finder instance. - */ - public function getForeignRecordFinder() - { - return TActiveRecord::finder($this->getForeignRecordClass()); - } - - /** - * Creates and return the TActiveRecordRelation handler for specific relationships. - * An instance of TActiveRecordHasOne, TActiveRecordBelongsTo, TActiveRecordHasMany, - * or TActiveRecordHasManyAssocation will be returned. - * @param TActiveRecordCriteria search criteria - * @return TActiveRecordRelation record relationship handler instnace. - * @throws TActiveRecordException if property is not defined or missing. - */ - public function getRelationHandler($criteria=null) - { - if(!$this->hasRecordRelation()) - { - throw new TActiveRecordException('ar_undefined_relation_prop', - $this->_property, get_class($this->_record), 'RELATIONS'); - } - if($criteria===null) - $criteria = new TActiveRecordCriteria($this->getCondition(), $this->getParameters()); - switch($this->getRelationType()) - { - case TActiveRecord::HAS_MANY: - Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany'); - return new TActiveRecordHasMany($this, $criteria); - case TActiveRecord::MANY_TO_MANY: - Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasManyAssociation'); - return new TActiveRecordHasManyAssociation($this, $criteria); - case TActiveRecord::HAS_ONE: - Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasOne'); - return new TActiveRecordHasOne($this, $criteria); - case TActiveRecord::BELONGS_TO: - Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordBelongsTo'); - return new TActiveRecordBelongsTo($this, $criteria); - default: - throw new TActiveRecordException('ar_invalid_relationship'); - } - } - - /** - * @return TActiveRecordRelationCommand - */ - public function updateAssociatedRecords($updateBelongsTo=false) - { - $success=true; - foreach($this->_record->getRecordRelations() as $data) - { - list($property, $relation) = $data; - $belongsTo = $relation[0]==TActiveRecord::BELONGS_TO; - if(($updateBelongsTo && $belongsTo) || (!$updateBelongsTo && !$belongsTo)) - { - $obj = $this->getSourceRecord(); - if(!$this->isEmptyFkObject($obj->getColumnValue($property))) - { - $context = new TActiveRecordRelationContext($this->getSourceRecord(),$property,$relation); - $success = $context->getRelationHandler()->updateAssociatedRecords() && $success; - } - } - } - return $success; - } - - protected function isEmptyFkObject($obj) - { - if(is_object($obj)) - return $obj instanceof TList ? $obj->count() === 0 : false; - else if(is_array($obj)) - return count($obj)===0; - else - return empty($obj); - } -} - diff --git a/framework/Testing/Data/ActiveRecord/TActiveFinder.php b/framework/Testing/Data/ActiveRecord/TActiveFinder.php deleted file mode 100644 index d1c35bdf..00000000 --- a/framework/Testing/Data/ActiveRecord/TActiveFinder.php +++ /dev/null @@ -1,1279 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2009 Yii Software LLC - * @license http://www.yiiframework.com/license/CBaseActiveRelation*/ - -/** - * CActiveFinder implements eager loading and lazy loading of related active records. - * - * When used in eager loading, this class provides the same set of find methods as - * {@link CActiveRecord}. - * - * @author Qiang Xue - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TActiveFinder extends TComponent -{ - /** - * @var boolean join all tables all at once. Defaults to false. - * This property is internally used. - * @since 1.0.2 - */ - public $joinAll=false; - /** - * @var boolean whether the base model has limit or offset. - * This property is internally used. - * @since 1.0.2 - */ - public $baseLimited=false; - - private $_joinCount=0; - private $_joinTree; - private $_builder; - private $_criteria; // the criteria generated via named scope - - /** - * Constructor. - * A join tree is built up based on the declared relationships between active record classes. - * @param CActiveRecord the model that initiates the active finding process - * @param mixed the relation names to be actively looked for - * @param CDbCriteria the criteria associated with the named scopes (since version 1.0.5) - */ - public function __construct($model,$with,$criteria=null) - { - $this->_criteria=$criteria; - $this->_builder=$model->getCommandBuilder(); - $this->_joinTree=new TJoinElement($this,$model); - $this->buildJoinTree($this->_joinTree,$with); - } - - /** - * Uses the most aggressive join approach. - * By calling this method, even if there is LIMIT/OFFSET option set for - * the primary table query, we will still use a single SQL statement. - * By default (without calling this method), the primary table will be queried - * by itself so that LIMIT/OFFSET can be correctly applied. - * @return CActiveFinder the finder object - * @since 1.0.2 - */ - public function together() - { - $this->joinAll=true; - return $this; - } - - private function query($criteria,$all=false) - { - if($this->_criteria!==null) - { - $this->_criteria->mergeWith($criteria); - $criteria=$this->_criteria; - } - - $this->_joinTree->find($criteria); - $this->_joinTree->afterFind(); - - if($all) - return array_values($this->_joinTree->records); - else if(count($this->_joinTree->records)) - return reset($this->_joinTree->records); - else - return null; - } - - /** - * This is the relational version of {@link CActiveRecord::find()}. - */ - public function find($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.find() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAll()}. - */ - public function findAll($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAll() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findByPk()}. - */ - public function findByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findByPk() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createPkCriteria($this->_joinTree->model->getTableSchema(),$pk,$condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAllByPk()}. - */ - public function findAllByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllByPk() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createPkCriteria($this->_joinTree->model->getTableSchema(),$pk,$condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findByAttributes()}. - */ - public function findByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findByAttributes() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createColumnCriteria($this->_joinTree->model->getTableSchema(),$attributes,$condition,$params); - return $this->query($criteria); - } - - /** - * This is the relational version of {@link CActiveRecord::findAllByAttributes()}. - */ - public function findAllByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllByAttributes() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createColumnCriteria($this->_joinTree->model->getTableSchema(),$attributes,$condition,$params); - return $this->query($criteria,true); - } - - /** - * This is the relational version of {@link CActiveRecord::findBySql()}. - */ - public function findBySql($sql,$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findBySql() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - if(($row=$this->_builder->createSqlCommand($sql,$params)->queryRow())!==false) - { - $baseRecord=$this->_joinTree->model->populateRecord($row,false); - $this->_joinTree->findWithBase($baseRecord); - $this->_joinTree->afterFind(); - return $baseRecord; - } - } - - /** - * This is the relational version of {@link CActiveRecord::findAllBySql()}. - */ - public function findAllBySql($sql,$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.findAllBySql() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - if(($rows=$this->_builder->createSqlCommand($sql,$params)->queryAll())!==array()) - { - $baseRecords=$this->_joinTree->model->populateRecords($rows,false); - $this->_joinTree->findWithBase($baseRecords); - $this->_joinTree->afterFind(); - return $baseRecords; - } - else - return array(); - } - - /** - * This is the relational version of {@link CActiveRecord::count()}. - * @since 1.0.3 - */ - public function count($condition='',$params=array()) - { - Prado::trace(get_class($this->_joinTree->model).'.count() eagerly','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->_builder->createCriteria($condition,$params); - if($this->_criteria!==null) - { - $this->_criteria->mergeWith($criteria); - $criteria=$this->_criteria; - } - return $this->_joinTree->count($criteria); - } - - /** - * Finds the related objects for the specified active record. - * This method is internally invoked by {@link CActiveRecord} to support lazy loading. - * @param CActiveRecord the base record whose related objects are to be loaded - */ - public function lazyFind($baseRecord) - { - $this->_joinTree->lazyFind($baseRecord); - if(!empty($this->_joinTree->children)) - { - $child=reset($this->_joinTree->children); - $child->afterFind(); - } - } - - /** - * Builds up the join tree representing the relationships involved in this query. - * @param CJoinElement the parent tree node - * @param mixed the names of the related objects relative to the parent tree node - * @param array additional query options to be merged with the relation - */ - private function buildJoinTree($parent,$with,$options=null) - { - if($parent instanceof CStatElement) - throw new TDbException('The STAT relation "'.($parent->relation->name).'" cannot have child relations.'); - - if(is_string($with)) - { - if(($pos=strrpos($with,'.'))!==false) - { - $parent=$this->buildJoinTree($parent,substr($with,0,$pos)); - $with=substr($with,$pos+1); - } - - // named scope - if(($pos=strpos($with,':'))!==false) - { - $scopes=explode(':',substr($with,$pos+1)); - $with=substr($with,0,$pos); - } - - if(isset($parent->children[$with])) - return $parent->children[$with]; - - if(($relation=$parent->model->getActiveRelation($with))===null) - throw new TDbException('Relation "'.$with.'" is not defined in active record class "'.get_class($parent->model).'".'); - - $relation=clone $relation; - $model=TActiveRecord::model($relation->className); - if(($scope=$model->defaultScope())!==array()) - $relation->mergeWith($scope); - if(isset($scopes) && !empty($scopes)) - { - $scs=$model->scopes(); - foreach($scopes as $scope) - { - if(isset($scs[$scope])) - $relation->mergeWith($scs[$scope]); - else - throw new TDbException('Active record class "'.get_class($model).'" does not have a scope named "'.$scope.'".'); - } - } - - // dynamic options - if($options!==null) - $relation->mergeWith($options); - - if($relation instanceof TStatRelation) - return new TStatElement($this,$relation,$parent); - else - { - $element=$parent->children[$with]=new TJoinElement($this,$relation,$parent,++$this->_joinCount); - if(!empty($relation->with)) - $this->buildJoinTree($element,$relation->with); - return $element; - } - } - - // $with is an array, keys are relation name, values are relation spec - foreach($with as $key=>$value) - { - if(is_string($value)) // the value is a relation name - $this->buildJoinTree($parent,$value); - else if(is_string($key) && is_array($value)) - $element=$this->buildJoinTree($parent,$key,$value); - } - } -} - - -/** - * CJoinElement represents a tree node in the join tree created by {@link CActiveFinder}. - * - * @author Qiang Xue - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TJoinElement -{ - /** - * @var integer the unique ID of this tree node - */ - public $id; - /** - * @var CActiveRelation the relation represented by this tree node - */ - public $relation; - /** - * @var CActiveRecord the model associated with this tree node - */ - public $model; - /** - * @var array list of active records found by the queries. They are indexed by primary key values. - */ - public $records=array(); - /** - * @var array list of child join elements - */ - public $children=array(); - /** - * @var array list of stat elements - * @since 1.0.4 - */ - public $stats=array(); - /** - * @var string table alias for this join element - */ - public $tableAlias; - - private $_finder; - private $_builder; - private $_parent; - private $_pkAlias; // string or name=>alias - private $_columnAliases=array(); // name=>alias - private $_joined=false; - private $_table; - private $_related=array(); // PK, relation name, related PK => true - - /** - * Constructor. - * @param CActiveFinder the finder - * @param mixed the relation (if the third parameter is not null) - * or the model (if the third parameter is null) associated with this tree node. - * @param CJoinElement the parent tree node - * @param integer the ID of this tree node that is unique among all the tree nodes - */ - public function __construct($finder,$relation,$parent=null,$id=0) - { - $this->_finder=$finder; - $this->id=$id; - if($parent!==null) - { - $this->relation=$relation; - $this->_parent=$parent; - $this->_builder=$parent->_builder; - $this->tableAlias=$relation->alias===null?$relation->name:$relation->alias; - $this->model=TActiveRecord::model($relation->className); - $this->_table=$this->model->getTableSchema(); - } - else // root element, the first parameter is the model. - { - $this->model=$relation; - $this->_builder=$relation->getCommandBuilder(); - $this->_table=$relation->getTableSchema(); - } - - // set up column aliases, such as t1_c2 - $table=$this->_table; - $prefix='t'.$id.'_c'; - foreach($table->getColumnNames() as $key=>$name) - { - $alias=$prefix.$key; - $this->_columnAliases[$name]=$alias; - if($table->primaryKey===$name) - $this->_pkAlias=$alias; - else if(is_array($table->primaryKey) && in_array($name,$table->primaryKey)) - $this->_pkAlias[$name]=$alias; - } - } - - /** - * Performs the recursive finding with the criteria. - * @param CDbCriteria the query criteria - */ - public function find($criteria=null) - { - if($this->_parent===null) // root element - { - $query=new TJoinQuery($this,$criteria); - $this->_finder->baseLimited=($criteria->offset>=0 || $criteria->limit>=0); - $this->buildQuery($query); - $this->_finder->baseLimited=false; - $this->runQuery($query); - } - else if(!$this->_joined && !empty($this->_parent->records)) // not joined before - { - $query=new TJoinQuery($this->_parent); - $this->_joined=true; - $query->join($this); - $this->buildQuery($query); - $this->_parent->runQuery($query); - } - - foreach($this->children as $child) // find recursively - $child->find(); - - foreach($this->stats as $stat) - $stat->query(); - } - - /** - * Performs lazy find with the specified base record. - * @param CActiveRecord the active record whose related object is to be fetched. - */ - public function lazyFind($baseRecord) - { - if(is_string($this->_table->primaryKey)) - $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord; - else - { - $pk=array(); - foreach($this->_table->primaryKey as $name) - $pk[$name]=$baseRecord->$name; - $this->records[serialize($pk)]=$baseRecord; - } - - foreach($this->stats as $stat) - $stat->query(); - - if(empty($this->children)) - return; - - $child=reset($this->children); - $query=new TJoinQuery($this); - $this->_joined=true; - $child->_joined=true; - $query->join($child); - if($child->relation instanceof THasManyRelation) - { - $query->limit=$child->relation->limit; - $query->offset=$child->relation->offset; - $this->_finder->baseLimited=($query->offset>=0 || $query->limit>=0); - $query->groups[]=$child->relation->group; - $query->havings[]=$child->relation->having; - } - $child->buildQuery($query); - $this->_finder->baseLimited=false; - $this->runQuery($query); - foreach($child->children as $c) - $c->find(); - } - - /** - * Performs the eager loading with the base records ready. - * @param mixed the available base record(s). - */ - public function findWithBase($baseRecords) - { - if(!is_array($baseRecords)) - $baseRecords=array($baseRecords); - if(is_string($this->_table->primaryKey)) - { - foreach($baseRecords as $baseRecord) - $this->records[$baseRecord->{$this->_table->primaryKey}]=$baseRecord; - } - else - { - foreach($baseRecords as $baseRecord) - { - $pk=array(); - foreach($this->_table->primaryKey as $name) - $pk[$name]=$baseRecord->$name; - $this->records[serialize($pk)]=$baseRecord; - } - } - - $query=new TJoinQuery($this); - $this->buildQuery($query); - if(count($query->joins)>1) - $this->runQuery($query); - foreach($this->children as $child) - $child->find(); - - foreach($this->stats as $stat) - $stat->query(); - } - - /** - * Count the number of primary records returned by the join statement. - * @param CDbCriteria the query criteria - * @return integer number of primary records. - * @since 1.0.3 - */ - public function count($criteria=null) - { - $query=new TJoinQuery($this,$criteria); - // ensure only one big join statement is used - $this->_finder->baseLimited=false; - $this->_finder->joinAll=true; - $this->buildQuery($query); - - if(is_string($this->_table->primaryKey)) - { - $prefix=$this->getColumnPrefix(); - $schema=$this->_builder->getSchema(); - $column=$prefix.$schema->quoteColumnName($this->_table->primaryKey); - } - else if($criteria->select!=='*') - $column=$criteria->select; - else - throw new TDbException('Unable to count records with composite primary keys. Please explicitly specify the SELECT option in the query criteria.'); - - $query->selects=array("COUNT(DISTINCT $column)"); - $query->orders=$query->groups=$query->havings=array(); - $command=$query->createCommand($this->_builder); - return $command->queryScalar(); - } - - /** - * Calls {@link CActiveRecord::afterFind} of all the records. - * @since 1.0.3 - */ - public function afterFind() - { - foreach($this->records as $record) - $record->afterFindInternal(); - foreach($this->children as $child) - $child->afterFind(); - } - - /** - * Builds the join query with all descendant HAS_ONE and BELONGS_TO nodes. - * @param CJoinQuery the query being built up - */ - public function buildQuery($query) - { - foreach($this->children as $child) - { - if($child->relation instanceof THasOneRelation || $child->relation instanceof TBelongsToRelation - || $this->_finder->joinAll || !$this->_finder->baseLimited && $child->relation->together) - { - $child->_joined=true; - $query->join($child); - $child->buildQuery($query); - } - } - } - - /** - * Executes the join query and populates the query results. - * @param CJoinQuery the query to be executed. - */ - public function runQuery($query) - { - $command=$query->createCommand($this->_builder); - foreach($command->queryAll() as $row) - $this->populateRecord($query,$row); - } - - /** - * Populates the active records with the query data. - * @param CJoinQuery the query executed - * @param array a row of data - * @return CActiveRecord the populated record - */ - private function populateRecord($query,$row) - { - // determine the primary key value - if(is_string($this->_pkAlias)) // single key - { - if(isset($row[$this->_pkAlias])) - $pk=$row[$this->_pkAlias]; - else // no matching related objects - return null; - } - else // is_array, composite key - { - $pk=array(); - foreach($this->_pkAlias as $name=>$alias) - { - if(isset($row[$alias])) - $pk[$name]=$row[$alias]; - else // no matching related objects - return null; - } - $pk=serialize($pk); - } - - // retrieve or populate the record according to the primary key value - if(isset($this->records[$pk])) - $record=$this->records[$pk]; - else - { - $attributes=array(); - $aliases=array_flip($this->_columnAliases); - foreach($row as $alias=>$value) - { - if(isset($aliases[$alias])) - $attributes[$aliases[$alias]]=$value; - } - $record=$this->model->populateRecord($attributes,false); - foreach($this->children as $child) - $record->addRelatedRecord($child->relation->name,null,$child->relation instanceof THasManyRelation); - $this->records[$pk]=$record; - } - - // populate child records recursively - foreach($this->children as $child) - { - if(!isset($query->elements[$child->id])) - continue; - $childRecord=$child->populateRecord($query,$row); - if($child->relation instanceof THasOneRelation || $child->relation instanceof TBelongsToRelation) - $record->addRelatedRecord($child->relation->name,$childRecord,false); - else // has_many and many_many - { - // need to double check to avoid adding duplicated related objects - if($childRecord instanceof TActiveRecord) - $fpk=serialize($childRecord->getPrimaryKey()); - else - $fpk=0; - if(!isset($this->_related[$pk][$child->relation->name][$fpk])) - { - $record->addRelatedRecord($child->relation->name,$childRecord,true); - $this->_related[$pk][$child->relation->name][$fpk]=true; - } - } - } - - return $record; - } - - /** - * @return string the table name and the table alias (if any). This can be used directly in SQL query without escaping. - */ - public function getTableNameWithAlias() - { - if($this->tableAlias!==null) - return $this->_table->rawName . ' ' . $this->tableAlias; - else - return $this->_table->rawName; - } - - /** - * Generates the list of columns to be selected. - * Columns will be properly aliased and primary keys will be added to selection if they are not specified. - * @param mixed columns to be selected. Defaults to '*', indicating all columns. - * @return string the column selection - */ - public function getColumnSelect($select='*') - { - $schema=$this->_builder->getSchema(); - $prefix=$this->getColumnPrefix(); - $columns=array(); - if($select==='*') - { - foreach($this->_table->getColumnNames() as $name) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_columnAliases[$name]); - } - else - { - if(is_string($select)) - $select=explode(',',$select); - $selected=array(); - foreach($select as $name) - { - $name=trim($name); - $matches=array(); - if(($pos=strrpos($name,'.'))!==false) - $key=substr($name,$pos+1); - else - $key=$name; - if(isset($this->_columnAliases[$key])) // simple column names - { - $columns[]=$prefix.$schema->quoteColumnName($key).' AS '.$schema->quoteColumnName($this->_columnAliases[$key]); - $selected[$this->_columnAliases[$key]]=1; - } - else if(preg_match('/^(.*?)\s+AS\s+(\w+)$/i',$name,$matches)) // if the column is already aliased - { - $alias=$matches[2]; - if(!isset($this->_columnAliases[$alias]) || $this->_columnAliases[$alias]!==$alias) - { - $this->_columnAliases[$alias]=$alias; - $columns[]=$name; - $selected[$alias]=1; - } - } - else - throw new TDbException('Active record "'.get_class($this->model).'" is trying to select an invalid column "'.$name.'". Note, the column must exist in the table or be an expression with alias.'); - } - // add primary key selection if they are not selected - if(is_string($this->_pkAlias) && !isset($selected[$this->_pkAlias])) - $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias); - else if(is_array($this->_pkAlias)) - { - foreach($this->_table->primaryKey as $name) - if(!isset($selected[$name])) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_pkAlias[$name]); - } - } - - return implode(', ',$columns); - } - - /** - * @return string the primary key selection - */ - public function getPrimaryKeySelect() - { - $schema=$this->_builder->getSchema(); - $prefix=$this->getColumnPrefix(); - $columns=array(); - if(is_string($this->_pkAlias)) - $columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias); - else if(is_array($this->_pkAlias)) - { - foreach($this->_pkAlias as $name=>$alias) - $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias); - } - return implode(', ',$columns); - } - - /** - * @return string the condition that specifies only the rows with the selected primary key values. - */ - public function getPrimaryKeyRange() - { - if(empty($this->records)) - return ''; - $values=array_keys($this->records); - if(is_array($this->_table->primaryKey)) - { - foreach($values as &$value) - $value=unserialize($value); - } - return $this->_builder->createInCondition($this->_table,$this->_table->primaryKey,$values,$this->getColumnPrefix()); - } - - /** - * @return string the column prefix for column reference disambiguation - */ - public function getColumnPrefix() - { - if($this->tableAlias!==null) - return $this->tableAlias.'.'; - else - return $this->_table->rawName.'.'; - } - - /** - * @return string the join statement (this node joins with its parent) - */ - public function getJoinCondition() - { - $parent=$this->_parent; - $relation=$this->relation; - if($this->relation instanceof TManyManyRelation) - { - if(!preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->relation->foreignKey,$matches)) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".'); - - $schema=$this->_builder->getSchema(); - if(($joinTable=$schema->getTable($matches[1]))===null) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is not specified correctly: the join table "'.($matches[1]).'" given in the foreign key cannot be found in the database.'); - - $fks=preg_split('/[\s,]+/',$matches[2],-1,PREG_SPLIT_NO_EMPTY); - - return $this->joinManyMany($joinTable,$fks,$parent); - } - else - { - $fks=preg_split('/[\s,]+/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY); - if($this->relation instanceof TBelongsToRelation) - { - $pke=$this; - $fke=$parent; - } - else - { - $pke=$parent; - $fke=$this; - } - return $this->joinOneMany($fke,$fks,$pke,$parent); - } - } - - /** - * Generates the join statement for one-many relationship. - * This works for HAS_ONE, HAS_MANY and BELONGS_TO. - * @param CJoinElement the join element containing foreign keys - * @param array the foreign keys - * @param CJoinElement the join element containg primary keys - * @param CJoinElement the parent join element - * @return string the join statement - * @throws CDbException if a foreign key is invalid - */ - private function joinOneMany($fke,$fks,$pke,$parent) - { - $schema=$this->_builder->getSchema(); - $joins=array(); - foreach($fks as $i=>$fk) - { - if(!isset($fke->_table->columns[$fk])) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($fke->_table->name).'".'); - - if(isset($fke->_table->foreignKeys[$fk])) - $pk=$fke->_table->foreignKeys[$fk][1]; - else // FK constraints undefined - { - if(is_array($pke->_table->primaryKey)) // composite PK - $pk=$pke->_table->primaryKey[$i]; - else - $pk=$pke->_table->primaryKey; - } - $joins[]=$fke->getColumnPrefix().$schema->quoteColumnName($fk) . '=' . $pke->getColumnPrefix().$schema->quoteColumnName($pk); - } - if(!empty($this->relation->on)) - $joins[]=$this->relation->on; - return $this->relation->joinType . ' ' . $this->getTableNameWithAlias() . ' ON (' . implode(') AND (',$joins).')'; - } - - /** - * Generates the join statement for many-many relationship. - * @param CDbTableSchema the join table - * @param array the foreign keys - * @param CJoinElement the parent join element - * @return string the join statement - * @throws CDbException if a foreign key is invalid - */ - private function joinManyMany($joinTable,$fks,$parent) - { - $schema=$this->_builder->getSchema(); - $joinAlias=$this->relation->name.'_'.$this->tableAlias; - $parentCondition=array(); - $childCondition=array(); - foreach($fks as $i=>$fk) - { - if(!isset($joinTable->columns[$fk])) - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($joinTable->name).'".'); - - if(isset($joinTable->foreignKeys[$fk])) - { - list($tableName,$pk)=$joinTable->foreignKeys[$fk]; - if(!isset($parentCondition[$pk]) && $schema->compareTableNames($parent->_table->rawName,$tableName)) - $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - else if(!isset($childCondition[$pk]) && $schema->compareTableNames($this->_table->rawName,$tableName)) - $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - else - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". The foreign key does not point to either joining table.'); - } - else // FK constraints not defined - { - if($i_table->primaryKey)) - { - $pk=is_array($parent->_table->primaryKey) ? $parent->_table->primaryKey[$i] : $parent->_table->primaryKey; - $parentCondition[$pk]=$parent->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - } - else - { - $j=$i-count($parent->_table->primaryKey); - $pk=is_array($this->_table->primaryKey) ? $this->_table->primaryKey[$j] : $this->_table->primaryKey; - $childCondition[$pk]=$this->getColumnPrefix().$schema->quoteColumnName($pk).'='.$joinAlias.'.'.$schema->quoteColumnName($fk); - } - } - } - if($parentCondition!==array() && $childCondition!==array()) - { - $join=$this->relation->joinType.' '.$joinTable->rawName.' '.$joinAlias; - $join.=' ON ('.implode(') AND (',$parentCondition).')'; - $join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias(); - $join.=' ON ('.implode(') AND (',$childCondition).')'; - if(!empty($this->relation->on)) - $join.=' AND ('.$this->relation->on.')'; - return $join; - } - else - throw new TDbException('The relation "'.($this->relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - } -} - - -/** - * CJoinQuery represents a JOIN SQL statement. - * - * @author Qiang Xue - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TJoinQuery -{ - /** - * @var array list of column selections - */ - public $selects=array(); - /** - * @var array list of join statement - */ - public $joins=array(); - /** - * @var array list of WHERE clauses - */ - public $conditions=array(); - /** - * @var array list of ORDER BY clauses - */ - public $orders=array(); - /** - * @var array list of GROUP BY clauses - */ - public $groups=array(); - /** - * @var array list of HAVING clauses - */ - public $havings=array(); - /** - * @var integer row limit - */ - public $limit=-1; - /** - * @var integer row offset - */ - public $offset=-1; - /** - * @var array list of query parameters - */ - public $params=array(); - /** - * @var array list of join element IDs (id=>true) - */ - public $elements=array(); - - /** - * Constructor. - * @param CJoinElement The root join tree. - * @param CDbCriteria the query criteria - */ - public function __construct($joinElement,$criteria=null) - { - if($criteria!==null) - { - $this->selects[]=$joinElement->getColumnSelect($criteria->select); - $this->joins[]=$joinElement->getTableNameWithAlias(); - $this->joins[]=$criteria->join; - $this->conditions[]=$criteria->condition; - $this->orders[]=$criteria->order; - $this->groups[]=$criteria->group; - $this->havings[]=$criteria->having; - $this->limit=$criteria->limit; - $this->offset=$criteria->offset; - $this->params=$criteria->params; - } - else - { - $this->selects[]=$joinElement->getPrimaryKeySelect(); - $this->joins[]=$joinElement->getTableNameWithAlias(); - $this->conditions[]=$joinElement->getPrimaryKeyRange(); - } - $this->elements[$joinElement->id]=true; - } - - /** - * Joins with another join element - * @param CJoinElement the element to be joined - */ - public function join($element) - { - $this->selects[]=$element->getColumnSelect($element->relation->select); - $this->conditions[]=$element->relation->condition; - $this->orders[]=$element->relation->order; - $this->joins[]=$element->getJoinCondition(); - $this->groups[]=$element->relation->group; - $this->havings[]=$element->relation->having; - - if(is_array($element->relation->params)) - { - if(is_array($this->params)) - $this->params=array_merge($this->params,$element->relation->params); - else - $this->params=$element->relation->params; - } - $this->elements[$element->id]=true; - } - - /** - * Creates the SQL statement. - * @param CDbCommandBuilder the command builder - * @return string the SQL statement - */ - public function createCommand($builder) - { - $sql='SELECT ' . implode(', ',$this->selects); - $sql.=' FROM ' . implode(' ',$this->joins); - - $conditions=array(); - foreach($this->conditions as $condition) - if($condition!=='') - $conditions[]=$condition; - if($conditions!==array()) - $sql.=' WHERE (' . implode(') AND (',$conditions).')'; - - $groups=array(); - foreach($this->groups as $group) - if($group!=='') - $groups[]=$group; - if($groups!==array()) - $sql.=' GROUP BY ' . implode(', ',$groups); - - $havings=array(); - foreach($this->havings as $having) - if($having!=='') - $havings[]=$having; - if($havings!==array()) - $sql.=' HAVING (' . implode(') AND (',$havings).')'; - - $orders=array(); - foreach($this->orders as $order) - if($order!=='') - $orders[]=$order; - if($orders!==array()) - $sql.=' ORDER BY ' . implode(', ',$orders); - - $sql=$builder->applyLimit($sql,$this->limit,$this->offset); - $command=$builder->getDbConnection()->createCommand($sql); - $builder->bindValues($command,$this->params); - return $command; - } -} - - -/** - * CStatElement represents STAT join element for {@link CActiveFinder}. - * - * @author Qiang Xue - * @version $Id: CActiveFinder.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0.4 - */ -class TStatElement -{ - /** - * @var CActiveRelation the relation represented by this tree node - */ - public $relation; - - private $_finder; - private $_parent; - - /** - * Constructor. - * @param CActiveFinder the finder - * @param CStatRelation the STAT relation - * @param CJoinElement the join element owning this STAT element - */ - public function __construct($finder,$relation,$parent) - { - $this->_finder=$finder; - $this->_parent=$parent; - $this->relation=$relation; - $parent->stats[]=$this; - } - - /** - * Performs the STAT query. - */ - public function query() - { - if(preg_match('/^\s*(.*?)\((.*)\)\s*$/',$this->relation->foreignKey,$matches)) - $this->queryManyMany($matches[1],$matches[2]); - else - $this->queryOneMany(); - } - - private function queryOneMany() - { - $relation=$this->relation; - $model=TActiveRecord::model($relation->className); - $builder=$model->getCommandBuilder(); - $schema=$builder->getSchema(); - $table=$model->getTableSchema(); - $pkTable=$this->_parent->model->getTableSchema(); - - $fks=preg_split('/[\s,]+/',$relation->foreignKey,-1,PREG_SPLIT_NO_EMPTY); - if(count($fks)!==count($pkTable->primaryKey)) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key. The columns in the key must match the primary keys of the table "'.($pkTable->name).'".'); - - // set up mapping between fk and pk columns - $map=array(); // pk=>fk - foreach($fks as $i=>$fk) - { - if(!isset($table->columns[$fk])) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($table->name).'".'); - - if(isset($table->foreignKeys[$fk])) - { - list($tableName,$pk)=$table->foreignKeys[$fk]; - if($schema->compareTableNames($pkTable->rawName,$tableName)) - $map[$pk]=$fk; - else - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($parent->model).'" is specified with a foreign key "'.$fk.'" that does not point to the parent table "'.($pkTable->name).'".'); - } - else // FK constraints undefined - { - if(is_array($table->primaryKey)) // composite PK - $map[$table->primaryKey[$i]]=$fk; - } - } - - $records=$this->_parent->records; - - $where=empty($relation->condition)?'' : ' WHERE ('.$relation->condition.')'; - $group=empty($relation->group)?'' : ', '.$relation->group; - $having=empty($relation->having)?'' : ' AND ('.$relation->having.')'; - $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order; - - $c=$schema->quoteColumnName('c'); - $s=$schema->quoteColumnName('s'); - - // generate and perform query - if(count($fks)===1) // single column FK - { - $col=$table->columns[$fks[0]]->rawName; - $sql="SELECT $col AS $c, {$relation->select} AS $s FROM {$table->rawName}" - .$where - ." GROUP BY $col".$group - ." HAVING ".$builder->createInCondition($table,$fks[0],array_keys($records)) - .$having.$order; - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - $stats=array(); - foreach($command->queryAll() as $row) - $stats[$row['c']]=$row['s']; - } - else // composite FK - { - $keys=array_keys($records); - foreach($keys as &$key) - { - $key2=unserialize($key); - $key=array(); - foreach($pkTable->primaryKey as $pk) - $key[$map[$pk]]=$key2[$pk]; - } - $cols=array(); - foreach($pkTable->primaryKey as $n=>$pk) - { - $name=$table->columns[$map[$pk]]->rawName; - $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n); - } - $sql='SELECT '.implode(', ',$cols).", {$relation->select} AS $s FROM {$table->rawName}" - .$where - .' GROUP BY '.implode(', ',array_keys($cols)).$group - .' HAVING '.$builder->createInCondition($table,$fks,$keys) - .$having.$order; - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - $stats=array(); - foreach($command->queryAll() as $row) - { - $key=array(); - foreach($pkTable->primaryKey as $n=>$pk) - $key[$pk]=$row['c'.$n]; - $stats[serialize($key)]=$row['s']; - } - } - - // populate the results into existing records - foreach($records as $pk=>$record) - $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false); - } - - private function queryManyMany($joinTableName,$keys) - { - $relation=$this->relation; - $model=TActiveRecord::model($relation->className); - $table=$model->getTableSchema(); - $builder=$model->getCommandBuilder(); - $schema=$builder->getSchema(); - $pkTable=$this->_parent->model->getTableSchema(); - - if(($joinTable=$builder->getSchema()->getTable($joinTableName))===null) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is not specified correctly. The join table "'.$joinTableName.'" given in the foreign key cannot be found in the database.'); - - $fks=preg_split('/[\s,]+/',$keys,-1,PREG_SPLIT_NO_EMPTY); - if(count($fks)!==count($table->primaryKey)+count($pkTable->primaryKey)) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - - $joinCondition=array(); - $map=array(); - foreach($fks as $i=>$fk) - { - if(!isset($joinTable->columns[$fk])) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an invalid foreign key "'.$fk.'". There is no such column in the table "'.($joinTable->name).'".'); - - if(isset($joinTable->foreignKeys[$fk])) - { - list($tableName,$pk)=$joinTable->foreignKeys[$fk]; - if(!isset($joinCondition[$pk]) && $schema->compareTableNames($table->rawName,$tableName)) - $joinCondition[$pk]=$table->rawName.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk); - else if(!isset($map[$pk]) && $schema->compareTableNames($pkTable->rawName,$tableName)) - $map[$pk]=$fk; - else - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an invalid foreign key "'.$fk.'". The foreign key does not point to either joining table.'); - } - else // FK constraints not defined - { - if($iprimaryKey)) - { - $pk=is_array($pkTable->primaryKey) ? $pkTable->primaryKey[$i] : $pkTable->primaryKey; - $map[$pk]=$fk; - } - else - { - $j=$i-count($pkTable->primaryKey); - $pk=is_array($table->primaryKey) ? $table->primaryKey[$j] : $table->primaryKey; - $joinCondition[$pk]=$table->rawName.'.'.$schema->quoteColumnName($pk).'='.$joinTable->rawName.'.'.$schema->quoteColumnName($fk); - } - } - } - - if($joinCondition===array() || $map===array()) - throw new TDbException('The relation "'.($relation->name).'" in active record class "'.get_class($this->_parent->model).'" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.'); - - $records=$this->_parent->records; - - $cols=array(); - foreach(is_string($pkTable->primaryKey)?array($pkTable->primaryKey):$pkTable->primaryKey as $n=>$pk) - { - $name=$joinTable->rawName.'.'.$schema->quoteColumnName($map[$pk]); - $cols[$name]=$name.' AS '.$schema->quoteColumnName('c'.$n); - } - - $keys=array_keys($records); - if(is_array($pkTable->primaryKey)) - { - foreach($keys as &$key) - { - $key2=unserialize($key); - $key=array(); - foreach($pkTable->primaryKey as $pk) - $key[$map[$pk]]=$key2[$pk]; - } - } - - $where=empty($relation->condition)?'' : ' WHERE ('.$relation->condition.')'; - $group=empty($relation->group)?'' : ', '.$relation->group; - $having=empty($relation->having)?'' : ' AND ('.$relation->having.')'; - $order=empty($relation->order)?'' : ' ORDER BY '.$relation->order; - - $sql='SELECT '.$this->relation->select.' AS '.$schema->quoteColumnName('s').', '.implode(', ',$cols) - .' FROM '.$table->rawName.' INNER JOIN '.$joinTable->rawName - .' ON ('.implode(') AND (',$joinCondition).')' - .$where - .' GROUP BY '.implode(', ',array_keys($cols)).$group - .' HAVING ('.$builder->createInCondition($joinTable,$map,$keys).')' - .$having.$order; - - $command=$builder->getDbConnection()->createCommand($sql); - if(is_array($relation->params)) - $builder->bindValues($command,$relation->params); - - $stats=array(); - foreach($command->queryAll() as $row) - { - if(is_array($pkTable->primaryKey)) - { - $key=array(); - foreach($pkTable->primaryKey as $n=>$k) - $key[$k]=$row['c'.$n]; - $stats[serialize($key)]=$row['s']; - } - else - $stats[$row['c0']]=$row['s']; - } - - foreach($records as $pk=>$record) - $record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$this->relation->defaultValue,false); - } -} diff --git a/framework/Testing/Data/ActiveRecord/TActiveRecord.php b/framework/Testing/Data/ActiveRecord/TActiveRecord.php deleted file mode 100644 index 8d6c5e10..00000000 --- a/framework/Testing/Data/ActiveRecord/TActiveRecord.php +++ /dev/null @@ -1,1843 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2009 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CActiveRecord is the base class for classes representing relational data. - * - * It implements the active record design pattern, a popular Object-Relational Mapping (ORM) technique. - * Please check {@link http://www.yiiframework.com/doc/guide/database.ar the Guide} for more details - * about this class. - * - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ - -Prado::using('System.Base.TModel'); -Prado::using('System.Testing.Data.*'); -Prado::using('System.Testing.Data.ActiveRecord.*'); -Prado::using('System.Base.TEvent'); -Prado::using('System.Testing.Data.ActiveRecord.TActiveRecordCriteria'); - -abstract class TActiveRecord extends TModel -{ - const BELONGS_TO='TBelongsToRelation'; - const HAS_ONE='THasOneRelation'; - const HAS_MANY='THasManyRelation'; - const MANY_MANY='TManyManyRelation'; - const STAT='TStatRelation'; - - /** - * @var CDbConnection the default database connection for all active record classes. - * By default, this is the 'db' application component. - * @see getDbConnection - */ - public static $db; - - private static $_models=array(); // class name => model - - private $_md; // meta data - private $_new=false; // whether this instance is new or not - private $_attributes=array(); // attribute name => attribute value - private $_related=array(); // attribute name => related objects - private $_c; // query criteria (used by finder only) - - /** - * Returns the instance of a active record finder for a particular class. - * The finder objects are static instances for each ActiveRecord class. - * This means that event handlers bound to these finder instances are class wide. - * Create a new instance of the ActiveRecord class if you wish to bound the - * event handlers to object instance. - * @param string active record class name. - * @return TActiveRecord active record finder instance. - */ - - /** - * Constructor. - * @param string scenario name. See {@link CModel::scenario} for more details about this parameter. - */ - public function __construct($scenario='insert') - { - if($scenario===null) // internally used by populateRecord() and model() - return; - - $this->setScenario($scenario); - $this->setIsNewRecord(true); - $this->_attributes=$this->getMetaData()->attributeDefaults; - - $this->attachBehaviors($this->behaviors()); - $this->afterConstruct(); - } - - /** - * PHP sleep magic method. - * This method ensures that the model meta data reference is set to null. - */ - public function __sleep() - { - $this->_md=null; - return array_keys((array)$this); - } - - /** - * PHP getter magic method. - * This method is overridden so that AR attributes can be accessed like properties. - * @param string property name - * @return mixed property value - * @see getAttribute - */ - public function __get($name) - { - if(isset($this->_attributes[$name])) - return $this->_attributes[$name]; - else if(isset($this->getMetaData()->columns[$name])) - return null; - else if(isset($this->_related[$name])) - return $this->_related[$name]; - else if(isset($this->getMetaData()->relations[$name])) - return $this->getRelated($name); - else - return parent::__get($name); - } - - /** - * PHP setter magic method. - * This method is overridden so that AR attributes can be accessed like properties. - * @param string property name - * @param mixed property value - */ - public function __set($name,$value) - { - if($this->setAttribute($name,$value)===false) - parent::__set($name,$value); - } - - /** - * Checks if a property value is null. - * This method overrides the parent implementation by checking - * if the named attribute is null or not. - * @param string the property name or the event name - * @return boolean whether the property value is null - * @since 1.0.1 - */ - public function __isset($name) - { - if(isset($this->_attributes[$name])) - return true; - else if(isset($this->getMetaData()->columns[$name])) - return false; - else if(isset($this->_related[$name])) - return true; - else if(isset($this->getMetaData()->relations[$name])) - return $this->getRelated($name)!==null; -# else -# return parent::__isset($name); - } - - /** - * Sets a component property to be null. - * This method overrides the parent implementation by clearing - * the specified attribute value. - * @param string the property name or the event name - * @since 1.0.1 - */ - public function __unset($name) - { - if(isset($this->getMetaData()->columns[$name])) - unset($this->_attributes[$name]); - else if(isset($this->getMetaData()->relations[$name])) - unset($this->_related[$name]); - else - parent::__unset($name); - } - - /** - * Calls the named method which is not a class method. - * Do not call this method. This is a PHP magic method that we override - * to implement the named scope feature. - * @param string the method name - * @param array method parameters - * @return mixed the method return value - * @since 1.0.5 - * - * @TODO: implement __call to parent - * - */ - public function __call($name,$parameters) - { - if(isset($this->getMetaData()->relations[$name])) - { - if(empty($parameters)) - return $this->getRelated($name,false); - else - return $this->getRelated($name,false,$parameters[0]); - } - - $scopes=$this->scopes(); - if(isset($scopes[$name])) - { - $this->getDbCriteria()->mergeWith($scopes[$name]); - return $this; - } - -# return parent::__call($name,$parameters);*/ - } - - /** - * Returns the related record(s). - * This method will return the related record(s) of the current record. - * If the relation is HAS_ONE or BELONGS_TO, it will return a single object - * or null if the object does not exist. - * If the relation is HAS_MANY or MANY_MANY, it will return an array of objects - * or an empty array. - * @param string the relation name (see {@link relations}) - * @param boolean whether to reload the related objects from database. Defaults to false. - * @param array additional parameters that customize the query conditions as specified in the relation declaration. - * This parameter has been available since version 1.0.5. - * @return mixed the related object(s). - * @throws CDbException if the relation is not specified in {@link relations}. - * @since 1.0.2 - */ - public function getRelated($name,$refresh=false,$params=array()) - { - if(!$refresh && $params===array() && (isset($this->_related[$name]) || array_key_exists($name,$this->_related))) - return $this->_related[$name]; - - $md=$this->getMetaData(); - if(!isset($md->relations[$name])) - throw new TDbException(get_class($this).' does not have relation "'.$name.'".'); - - Prado::trace('lazy loading '.get_class($this).'.'.$name,'System.Testing.Data.ActiveRecord.TActiveRecord'); - $relation=$md->relations[$name]; - if($this->getIsNewRecord() && ($relation instanceof THasOneRelation || $relation instanceof THasManyRelation)) - return $relation instanceof THasOneRelation ? null : array(); - - if($params!==array()) // dynamic query - { - $exists=isset($this->_related[$name]) || array_key_exists($name,$this->_related); - if($exists) - $save=$this->_related[$name]; - unset($this->_related[$name]); - $r=array($name=>$params); - } - else - $r=$name; - - $finder=new TActiveFinder($this,$r); - $finder->lazyFind($this); - - if(!isset($this->_related[$name])) - { - if($relation instanceof THasManyRelation) - $this->_related[$name]=array(); - else if($relation instanceof TStatRelation) - $this->_related[$name]=$relation->defaultValue; - else - $this->_related[$name]=null; - } - - if($params!==array()) - { - $results=$this->_related[$name]; - if($exists) - $this->_related[$name]=$save; - else - unset($this->_related[$name]); - return $results; - } - else - return $this->_related[$name]; - } - - /** - * Returns a value indicating whether the named related object(s) has been loaded. - * @param string the relation name - * @return booolean a value indicating whether the named related object(s) has been loaded. - * @since 1.0.3 - */ - public function hasRelated($name) - { - return isset($this->_related[$name]) || array_key_exists($name,$this->_related); - } - - /** - * Returns the query criteria associated with this model. - * @param boolean whether to create a criteria instance if it does not exist. Defaults to true. - * @return CDbCriteria the query criteria that is associated with this model. - * This criteria is mainly used by {@link scopes named scope} feature to accumulate - * different criteria specifications. - * @since 1.0.5 - */ - public function getDbCriteria($createIfNull=true) - { - if($this->_c===null) - { - if(($c=$this->defaultScope())!==array() || $createIfNull) - $this->_c=new TDbCriteria($c); - } - return $this->_c; - } - - /** - * Returns the default named scope that should be applied to all queries implicitly for this model. - * The default implementation simply returns an empty array. You may override this method - * if the model needs to be queried with some default criteria (e.g. only active records should be returned). - * @return array the query criteria. This will be used as the parameter to the constructor - * of {@link CDbCriteria}. - * @since 1.0.5 - */ - public function defaultScope() - { - return array(); - } - - /** - * Returns the static model of the specified AR class. - * The model returned is a static instance of the AR class. - * It is provided for invoking class-level methods (something similar to static class methods.) - * - * EVERY derived AR class must override this method as follows, - *
-	 * public static function model($className=__CLASS__)
-	 * {
-	 *     return parent::model($className);
-	 * }
-	 * 
- * - * @param string active record class name. - * @return CActiveRecord active record model instance. - */ - public static function model($className=__CLASS__) - { - if(isset(self::$_models[$className])) - return self::$_models[$className]; - else - { - $model=self::$_models[$className]=new $className(null); - $model->attachBehaviors($model->behaviors()); - $model->_md=new TActiveRecordMetaData($model); - return $model; - } - } - - /** - * @return CActiveRecordMetaData the meta for this AR class. - */ - public function getMetaData() - { - if($this->_md!==null) - return $this->_md; - else - return $this->_md=self::model(get_class($this))->_md; - } - - /** - * Returns the name of the associated database table. - * By default this method returns the class name as the table name. - * You may override this method if the table is not named after this convention. - * @return string the table name - */ - public function tableName() - { - return get_class($this); - } - - /** - * Returns the primary key of the associated database table. - * This method is meant to be overridden in case when the table is not defined with a primary key - * (for some legency database). If the table is already defined with a primary key, - * you do not need to override this method. The default implementation simply returns null, - * meaning using the primary key defined in the database. - * @return mixed the primary key of the associated database table. - * If the key is a single column, it should return the column name; - * If the key is a composite one consisting of several columns, it should - * return the array of the key column names. - * @since 1.0.4 - */ - public function primaryKey() - { - } - - /** - * This method should be overridden to declare related objects. - * - * There are four types of relations that may exist between two active record objects: - *
    - *
  • BELONGS_TO: e.g. a member belongs to a team;
  • - *
  • HAS_ONE: e.g. a member has at most one profile;
  • - *
  • HAS_MANY: e.g. a team has many members;
  • - *
  • MANY_MANY: e.g. a member has many skills and a skill belongs to a member.
  • - *
- * - * By declaring these relations, CActiveRecord can bring back related objects - * in either lazy loading or eager loading approach, and you save the effort of - * writing complex JOIN SQL statements. - * - * Each kind of related objects is defined in this method as an array with the following elements: - *
-	 * 'varName'=>array('relationType', 'className', 'foreignKey', ...additional options)
-	 * 
- * where 'varName' refers to the name of the variable/property that the related object(s) can - * be accessed through; 'relationType' refers to the type of the relation, which can be one of the - * following four constants: self::BELONGS_TO, self::HAS_ONE, self::HAS_MANY and self::MANY_MANY; - * 'className' refers to the name of the active record class that the related object(s) is of; - * and 'foreignKey' states the foreign key that relates the two kinds of active record. - * Note, for composite foreign keys, they must be listed together, separating with space or comma; - * and for foreign keys used in MANY_MANY relation, the joining table must be declared as well - * (e.g. 'joinTable(fk1, fk2)'). - * - * Additional options may be specified as name-value pairs in the rest array elements: - *
    - *
  • 'select': string|array, a list of columns to be selected. Defaults to '*', meaning all columns. - * Column names should be disambiguated if they appear in an expression (e.g. COUNT(??.name) AS name_count).
  • - *
  • 'condition': string, the WHERE clause. Defaults to empty. Note, column references need to - * be disambiguated with prefix '??.' (e.g. ??.age>20)
  • - *
  • 'order': string, the ORDER BY clause. Defaults to empty. Note, column references need to - * be disambiguated with prefix '??.' (e.g. ??.age DESC)
  • - *
  • 'with': string|array, a list of child related objects that should be loaded together with this object. - * Note, this is only honored by lazy loading, not eager loading.
  • - *
  • 'joinType': type of join. Defaults to 'LEFT OUTER JOIN'.
  • - *
  • 'alias': the alias for the table associated with this relationship. - * This option has been available since version 1.0.1. It defaults to null, - * meaning the table alias is the same as the relation name.
  • - *
  • 'params': the parameters to be bound to the generated SQL statement. - * This should be given as an array of name-value pairs. This option has been - * available since version 1.0.3.
  • - *
  • 'on': the ON clause. The condition specified here will be appended - * to the joining condition using the AND operator. This option has been - * available since version 1.0.2.
  • - *
- * - * The following options are available for certain relations when lazy loading: - *
    - *
  • 'group': string, the GROUP BY clause. Defaults to empty. Note, column references need to - * be disambiguated with prefix '??.' (e.g. ??.age). This option only applies to HAS_MANY and MANY_MANY relations.
  • - *
  • 'having': string, the HAVING clause. Defaults to empty. Note, column references need to - * be disambiguated with prefix '??.' (e.g. ??.age). This option only applies to HAS_MANY and MANY_MANY relations.
  • - *
  • 'limit': limit of the rows to be selected. This option does not apply to BELONGS_TO relation.
  • - *
  • 'offset': offset of the rows to be selected. This option does not apply to BELONGS_TO relation.
  • - *
- * - * Below is an example declaring related objects for 'Post' active record class: - *
-	 * return array(
-	 *     'author'=>array(self::BELONGS_TO, 'User', 'authorID'),
-	 *     'comments'=>array(self::HAS_MANY, 'Comment', 'postID', 'with'=>'author', 'order'=>'createTime DESC'),
-	 *     'tags'=>array(self::MANY_MANY, 'Tag', 'PostTag(postID, tagID)', 'order'=>'name'),
-	 * );
-	 * 
- * - * @return array list of related object declarations. Defaults to empty array. - */ - public function relations() - { - return array(); - } - - /** - * Returns the declaration of named scopes. - * A named scope represents a query criteria that can be chained together with - * other named scopes and applied to a query. This method should be overridden - * by child classes to declare named scopes for the particular AR classes. - * For example, the following code declares two named scopes: 'recently' and - * 'published'. - *
-	 * return array(
-	 *     'published'=>array(
-	 *           'condition'=>'status=1',
-	 *     ),
-	 *     'recently'=>array(
-	 *           'order'=>'createTime DESC',
-	 *           'limit'=>5,
-	 *     ),
-	 * );
-	 * 
- * If the above scopes are declared in a 'Post' model, we can perform the following - * queries: - *
-	 * $posts=Post::model()->published()->findAll();
-	 * $posts=Post::model()->published()->recently()->findAll();
-	 * $posts=Post::model()->published()->with('comments')->findAll();
-	 * 
- * Note that the last query is a relational query. - * - * @return array the scope definition. The array keys are scope names; the array - * values are the corresponding scope definitions. Each scope definition is represented - * as an array whose keys must be properties of {@link CDbCriteria}. - * @since 1.0.5 - */ - public function scopes() - { - return array(); - } - - /** - * Returns the list of all attribute names of the model. - * This would return all column names of the table associated with this AR class. - * @return array list of attribute names. - * @since 1.0.1 - */ - public function attributeNames() - { - return array_keys($this->getMetaData()->columns); - } - - /** - * Returns the database connection used by active record. - * By default, the "db" application component is used as the database connection. - * You may override this method if you want to use a different database connection. - * @return CDbConnection the database connection used by active record. - * - * @TODO Get default db connection if theres none set - * - */ - public function getDbConnection() - { - if(self::$db!==null) - return self::$db; - else - { - self::$db=Prado::getApplication()->getModule($this->getConnectionId())->getDbConnection(); - if(self::$db instanceof TDbConnection) - { - self::$db->setActive(true); - return self::$db; - } - else - throw new TDbException('ActiveRecord requires a db connection.'); - } - } - - /** - * Returns connection id (must be overloaded by child classes) - * @return string Id of TDbConnection module - */ - abstract public function getConnectionId(); - - /** - * @param string the relation name - * @return CActiveRelation the named relation declared for this AR class. Null if the relation does not exist. - */ - public function getActiveRelation($name) - { - return isset($this->getMetaData()->relations[$name]) ? $this->getMetaData()->relations[$name] : null; - } - - /** - * @return CDbTableSchema the metadata of the table that this AR belongs to - */ - public function getTableSchema() - { - return $this->getMetaData()->tableSchema; - } - - /** - * @return CDbCommandBuilder the command builder used by this AR - */ - public function getCommandBuilder() - { - return $this->getDbConnection()->getSchema()->getCommandBuilder(); - } - - /** - * @param string attribute name - * @return boolean whether this AR has the named attribute (table column). - */ - public function hasAttribute($name) - { - return isset($this->getMetaData()->columns[$name]); - } - - /** - * Returns the named attribute value. - * If this is a new record and the attribute is not set before, - * the default column value will be returned. - * If this record is the result of a query and the attribute is not loaded, - * null will be returned. - * You may also use $this->AttributeName to obtain the attribute value. - * @param string the attribute name - * @return mixed the attribute value. Null if the attribute is not set or does not exist. - * @see hasAttribute - */ - public function getAttribute($name) - { - if(property_exists($this,$name)) - return $this->$name; - else if(isset($this->_attributes[$name])) - return $this->_attributes[$name]; - } - - /** - * Sets the named attribute value. - * You may also use $this->AttributeName to set the attribute value. - * @param string the attribute name - * @param mixed the attribute value. - * @return boolean whether the attribute exists and the assignment is conducted successfully - * @see hasAttribute - */ - public function setAttribute($name,$value) - { - if(property_exists($this,$name)) - $this->$name=$value; - else if(isset($this->getMetaData()->columns[$name])) - $this->_attributes[$name]=$value; - else - return false; - return true; - } - - /** - * Adds a related object to this record. - * This method is used internally by {@link CActiveFinder} to populate related objects. - * @param string attribute name - * @param mixed the related record - * @param boolean whether the relation is HAS_MANY/MANY_MANY. - */ - public function addRelatedRecord($name,$record,$multiple) - { - if($multiple) - { - if(!isset($this->_related[$name])) - $this->_related[$name]=array(); - if($record instanceof TActiveRecord) - $this->_related[$name][]=$record; - } - else if(!isset($this->_related[$name])) - $this->_related[$name]=$record; - } - - /** - * Returns all column attribute values. - * Note, related objects are not returned. - * @param mixed names of attributes whose value needs to be returned. - * If this is true (default), then all attribute values will be returned, including - * those that are not loaded from DB (null will be returned for those attributes). - * If this is null, all attributes except those that are not loaded from DB will be returned. - * @return array attribute values indexed by attribute names. - */ - public function getAttributes($names=true) - { - $attributes=$this->_attributes; - foreach($this->getMetaData()->columns as $name=>$column) - { - if(property_exists($this,$name)) - $attributes[$name]=$this->$name; - else if($names===true && !isset($attributes[$name])) - $attributes[$name]=null; - } - if(is_array($names)) - { - $attrs=array(); - foreach($names as $name) - $attrs[$name]=isset($attributes[$name])?$attributes[$name]:null; - return $attrs; - } - else - return $attributes; - } - - /** - * Saves the current record. - * - * The record is inserted as a row into the database table if its {@link isNewRecord} - * property is true (usually the case when the record is created using the 'new' - * operator). Otherwise, it will be used to update the corresponding row in the table - * (usually the case if the record is obtained using one of those 'find' methods.) - * - * Validation will be performed before saving the record. If the validation fails, - * the record will not be saved. You can call {@link getErrors()} to retrieve the - * validation errors. - * - * If the record is saved via insertion, its {@link isNewRecord} property will be - * set false, and its {@link scenario} property will be set to be 'update'. - * And if its primary key is auto-incremental and is not set before insertion, - * the primary key will be populated with the automatically generated key value. - * - * @param boolean whether to perform validation before saving the record. - * If the validation fails, the record will not be saved to database. - * @param array list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the saving succeeds - */ - public function save($runValidation=true,$attributes=null) - { - if(!$runValidation || $this->validate($attributes)) - return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes); - else - return false; - } - - /** - * @return boolean whether the record is new and should be inserted when calling {@link save}. - * This property is automatically set in constructor and {@link populateRecord}. - * Defaults to false, but it will be set to true if the instance is created using - * the new operator. - */ - public function getIsNewRecord() - { - return $this->_new; - } - - /** - * @param boolean whether the record is new and should be inserted when calling {@link save}. - * @see getIsNewRecord - */ - public function setIsNewRecord($value) - { - $this->_new=$value; - } - - /** - * This event is raised before the record is saved. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onBeforeSave($event) - { - $this->raiseEvent('onBeforeSave',$event); - } - - /** - * This event is raised after the record is saved. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onAfterSave($event) - { - $this->raiseEvent('onAfterSave',$event); - } - - /** - * This event is raised before the record is deleted. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onBeforeDelete($event) - { - $this->raiseEvent('onBeforeDelete',$event); - } - - /** - * This event is raised after the record is deleted. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onAfterDelete($event) - { - $this->raiseEvent('onAfterDelete',$event); - } - - /** - * This event is raised after the record instance is created by new operator. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onAfterConstruct($event) - { - $this->raiseEvent('onAfterConstruct',$event); - } - - /** - * This event is raised after the record is instantiated by a find method. - * @param CEvent the event parameter - * @since 1.0.2 - */ - public function onAfterFind($event) - { - $this->raiseEvent('onAfterFind',$event); - } - - /** - * This method is invoked before saving a record (after validation, if any). - * The default implementation raises the {@link onBeforeSave} event. - * You may override this method to do any preparation work for record saving. - * Use {@link isNewRecord} to determine whether the saving is - * for inserting or updating record. - * Make sure you call the parent implementation so that the event is raised properly. - * @return boolean whether the saving should be executed. Defaults to true. - */ - protected function beforeSave() - { - $this->onBeforeSave(new TEvent($this)); - return true; - } - - /** - * This method is invoked after saving a record. - * The default implementation raises the {@link onAfterSave} event. - * You may override this method to do postprocessing after record saving. - * Make sure you call the parent implementation so that the event is raised properly. - */ - protected function afterSave() - { - $this->onAfterSave(new TEvent($this)); - } - - /** - * This method is invoked before deleting a record. - * The default implementation raises the {@link onBeforeDelete} event. - * You may override this method to do any preparation work for record deletion. - * Make sure you call the parent implementation so that the event is raised properly. - * @return boolean whether the record should be deleted. Defaults to true. - */ - protected function beforeDelete() - { - $this->onBeforeDelete(new TEvent($this)); - return true; - } - - /** - * This method is invoked after deleting a record. - * The default implementation raises the {@link onAfterDelete} event. - * You may override this method to do postprocessing after the record is deleted. - * Make sure you call the parent implementation so that the event is raised properly. - */ - protected function afterDelete() - { - $this->onAfterDelete(new TEvent($this)); - } - - /** - * This method is invoked after a record instance is created by new operator. - * The default implementation raises the {@link onAfterConstruct} event. - * You may override this method to do postprocessing after record creation. - * Make sure you call the parent implementation so that the event is raised properly. - */ - protected function afterConstruct() - { - $this->onAfterConstruct(new TEvent($this)); - } - - /** - * This method is invoked after each record is instantiated by a find method. - * The default implementation raises the {@link onAfterFind} event. - * You may override this method to do postprocessing after each newly found record is instantiated. - * Make sure you call the parent implementation so that the event is raised properly. - */ - protected function afterFind() - { - $this->onAfterFind(new TEvent($this)); - } - - /** - * Calls {@link afterFind}. - * This method is internally used. - * @since 1.0.3 - */ - public function afterFindInternal() - { - $this->afterFind(); - } - - /** - * Inserts a row into the table based on this active record attributes. - * If the table's primary key is auto-incremental and is null before insertion, - * it will be populated with the actual value after insertion. - * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. - * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false, - * and its {@link scenario} property will be set to be 'update'. - * @param array list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the attributes are valid and the record is inserted successfully. - * @throws CException if the record is not new - */ - public function insert($attributes=null) - { - if(!$this->getIsNewRecord()) - throw new TDbException('The active record cannot be inserted to database because it is not new.'); - if($this->beforeSave()) - { - Prado::trace(get_class($this).'.insert()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $table=$this->getMetaData()->tableSchema; - $command=$builder->createInsertCommand($table,$this->getAttributes($attributes)); - if($command->execute()) - { - $primaryKey=$table->primaryKey; - if($table->sequenceName!==null) - { - if(is_string($primaryKey) && $this->$primaryKey===null) - $this->$primaryKey=$builder->getLastInsertID($table); - else if(is_array($primaryKey)) - { - foreach($primaryKey as $pk) - { - if($this->$pk===null) - { - $this->$pk=$builder->getLastInsertID($table); - break; - } - } - } - } - $this->afterSave(); - $this->setIsNewRecord(false); - $this->setScenario('update'); - return true; - } - else - $this->afterSave(); - } - else - return false; - } - - /** - * Updates the row represented by this active record. - * All loaded attributes will be saved to the database. - * Note, validation is not performed in this method. You may call {@link validate} to perform the validation. - * @param array list of attributes that need to be saved. Defaults to null, - * meaning all attributes that are loaded from DB will be saved. - * @return boolean whether the update is successful - * @throws CException if the record is new - */ - public function update($attributes=null) - { - if($this->getIsNewRecord()) - throw new TDbException('The active record cannot be updated because it is new.'); - if($this->beforeSave()) - { - Prado::trace(get_class($this).'.update()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $this->updateByPk($this->getPrimaryKey(),$this->getAttributes($attributes)); - $this->afterSave(); - return true; - } - else - return false; - } - - /** - * Saves a selected list of attributes. - * Unlike {@link save}, this method only saves the specified attributes - * of an existing row dataset. It thus has better performance. - * Note, this method does neither attribute filtering nor validation. - * So do not use this method with untrusted data (such as user posted data). - * You may consider the following alternative if you want to do so: - *
-	 * $postRecord=Post::model()->findByPk($postID);
-	 * $postRecord->attributes=$_POST['post'];
-	 * $postRecord->save();
-	 * 
- * @param array attributes to be updated. Each element represents an attribute name - * or an attribute value indexed by its name. If the latter, the record's - * attribute will be changed accordingly before saving. - * @return boolean whether the update is successful - * @throws CException if the record is new or any database error - */ - public function saveAttributes($attributes) - { - if(!$this->getIsNewRecord()) - { - Prado::trace(get_class($this).'.saveAttributes()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $values=array(); - foreach($attributes as $name=>$value) - { - if(is_integer($name)) - $values[$value]=$this->$value; - else - $values[$name]=$this->$name=$value; - } - return $this->updateByPk($this->getPrimaryKey(),$values)>0; - } - else - throw new TDbException('The active record cannot be updated because it is new.'); - } - - /** - * Deletes the row corresponding to this active record. - * @return boolean whether the deletion is successful. - * @throws CException if the record is new - */ - public function delete() - { - if(!$this->getIsNewRecord()) - { - Prado::trace(get_class($this).'.delete()','System.Testing.Data.ActiveRecord.TActiveRecord'); - if($this->beforeDelete()) - { - $result=$this->deleteByPk($this->getPrimaryKey())>0; - $this->afterDelete(); - return $result; - } - else - return false; - } - else - throw new TDbException('The active record cannot be deleted because it is new.'); - } - - /** - * Repopulates this active record with the latest data. - * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record. - */ - public function refresh() - { - Prado::trace(get_class($this).'.refresh()','System.Testing.Data.ActiveRecord.TActiveRecord'); - if(!$this->getIsNewRecord() && ($record=$this->findByPk($this->getPrimaryKey()))!==null) - { - $this->_attributes=array(); - $this->_related=array(); - foreach($this->getMetaData()->columns as $name=>$column) - $this->$name=$record->$name; - return true; - } - else - return false; - } - - /** - * Compares this active record with another one. - * The comparison is made by comparing the primary key values of the two active records. - * @return boolean whether the two active records refer to the same row in the database table. - */ - public function equals($record) - { - return $this->tableName()===$record->tableName() && $this->getPrimaryKey()===$record->getPrimaryKey(); - } - - /** - * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite. - * If primary key is not defined, null will be returned. - */ - public function getPrimaryKey() - { - $table=$this->getMetaData()->tableSchema; - if(is_string($table->primaryKey)) - return $this->{$table->primaryKey}; - else if(is_array($table->primaryKey)) - { - $values=array(); - foreach($table->primaryKey as $name) - $values[$name]=$this->$name; - return $values; - } - else - return null; - } - - private function query($criteria,$all=false) - { - $this->applyScopes($criteria); - $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria); - return $all ? $this->populateRecords($command->queryAll()) : $this->populateRecord($command->queryRow()); - } - - private function applyScopes(&$criteria) - { - if(($c=$this->getDbCriteria(false))!==null) - { - $c->mergeWith($criteria); - $criteria=$c; - $this->_c=null; - } - } - - /** - * Finds a single active record with the specified condition. - * @param mixed query condition or criteria. - * If a string, it is treated as query condition (the WHERE clause); - * If an array, it is treated as the initial values for constructing a {@link CDbCriteria} object; - * Otherwise, it should be an instance of {@link CDbCriteria}. - * @param array parameters to be bound to an SQL statement. - * This is only used when the first parameter is a string (query condition). - * In other cases, please use {@link CDbCriteria::params} to set parameters. - * @return CActiveRecord the record found. Null if no record is found. - */ - public function find($condition='',$params=array()) - { - Prado::trace(get_class($this).'.find()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createCriteria($condition,$params); - $criteria->limit=1; - return $this->query($criteria); - } - - /** - * Finds all active records satisfying the specified condition. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return array list of active records satisfying the specified condition. An empty array is returned if none is found. - */ - public function findAll($condition='',$params=array()) - { - Prado::trace(get_class($this).'.findAll()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createCriteria($condition,$params); - return $this->query($criteria,true); - } - - /** - * Finds a single active record with the specified primary key. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. - */ - public function findByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.findByPk()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params); - $criteria->limit=1; - return $this->query($criteria); - } - - /** - * Finds all active records with the specified primary keys. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return array the records found. An empty array is returned if none is found. - */ - public function findAllByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.findAllByPk()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),$pk,$condition,$params); - return $this->query($criteria,true); - } - - /** - * Finds a single active record that has the specified attribute values. - * See {@link find()} for detailed explanation about $condition and $params. - * @param array list of attribute values (indexed by attribute names) that the active records should match. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return CActiveRecord the record found. Null if none is found. - */ - public function findByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.findByAttributes()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params); - $criteria->limit=1; - return $this->query($criteria); - } - - /** - * Finds all active records that have the specified attribute values. - * See {@link find()} for detailed explanation about $condition and $params. - * @param array list of attribute values (indexed by attribute names) that the active records should match. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return array the records found. An empty array is returned if none is found. - */ - public function findAllByAttributes($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.findAllByAttributes()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $criteria=$this->getCommandBuilder()->createColumnCriteria($this->getTableSchema(),$attributes,$condition,$params); - return $this->query($criteria,true); - } - - /** - * Finds a single active record with the specified SQL statement. - * @param string the SQL statement - * @param array parameters to be bound to the SQL statement - * @return CActiveRecord the record found. Null if none is found. - */ - public function findBySql($sql,$params=array()) - { - Prado::trace(get_class($this).'.findBySql()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $command=$this->getCommandBuilder()->createSqlCommand($sql,$params); - return $this->populateRecord($command->queryRow()); - } - - /** - * Finds all active records using the specified SQL statement. - * @param string the SQL statement - * @param array parameters to be bound to the SQL statement - * @return array the records found. An empty array is returned if none is found. - */ - public function findAllBySql($sql,$params=array()) - { - Prado::trace(get_class($this).'.findAllBySql()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $command=$this->getCommandBuilder()->createSqlCommand($sql,$params); - return $this->populateRecords($command->queryAll()); - } - - /** - * Finds the number of rows satisfying the specified query condition. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows satisfying the specified query condition. - */ - public function count($condition='',$params=array()) - { - Prado::trace(get_class($this).'.count()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createCriteria($condition,$params); - $this->applyScopes($criteria); - return $builder->createCountCommand($this->getTableSchema(),$criteria)->queryScalar(); - } - - /** - * Finds the number of rows using the given SQL statement. - * See {@link find()} for detailed explanation about $condition and $params. - * @param string the SQL statement - * @param array parameters to be bound to the SQL statement - * @return integer the number of rows using the given SQL statement. - */ - public function countBySql($sql,$params=array()) - { - Prado::trace(get_class($this).'.countBySql()','System.Testing.Data.ActiveRecord.TActiveRecord'); - return $this->getCommandBuilder()->createSqlCommand($sql,$params)->queryScalar(); - } - - /** - * Checks whether there is row satisfying the specified condition. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return boolean whether there is row satisfying the specified condition. - */ - public function exists($condition,$params=array()) - { - Prado::trace(get_class($this).'.exists()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createCriteria($condition,$params); - $table=$this->getTableSchema(); - $criteria->select=reset($table->columns)->rawName; - $criteria->limit=1; - $this->applyScopes($criteria); - return $builder->createFindCommand($table,$criteria)->queryRow()!==false; - } - - /** - * Specifies which related objects should be eagerly loaded. - * This method takes variable number of parameters. Each parameter specifies - * the name of a relation or child-relation. For example, - *
-	 * // find all posts together with their author and comments
-	 * Post::model()->with('author','comments')->findAll();
-	 * // find all posts together with their author and the author's profile
-	 * Post::model()->with('author','author.profile')->findAll();
-	 * 
- * The relations should be declared in {@link relations()}. - * - * By default, the options specified in {@link relations()} will be used - * to do relational query. In order to customize the options on the fly, - * we should pass an array parameter to the with() method. The array keys - * are relation names, and the array values are the corresponding query options. - * For example, - *
-	 * Post::model()->with(array(
-	 *     'author'=>array('select'=>'id, name'),
-	 *     'comments'=>array('condition'=>'approved=1', 'order'=>'createTime'),
-	 * ))->findAll();
-	 * 
- * - * This method returns a {@link CActiveFinder} instance that provides - * a set of find methods similar to that of CActiveRecord. - * - * Note, the possible parameters to this method have been changed since version 1.0.2. - * Previously, it was not possible to specify on-th-fly query options, - * and child-relations were specified as hierarchical arrays. - * - * @return CActiveFinder the active finder instance. If no parameter is passed in, the object itself will be returned. - */ - public function with() - { - if(func_num_args()>0) - { - $with=func_get_args(); - if(is_array($with[0])) // the parameter is given as an array - $with=$with[0]; - $finder=new TActiveFinder($this,$with,$this->getDbCriteria(false)); - $this->_c=null; - return $finder; - } - else - return $this; - } - - /** - * Updates records with the specified primary key(s). - * See {@link find()} for detailed explanation about $condition and $params. - * Note, the attributes are not checked for safety and validation is NOT performed. - * @param mixed primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). - * @param array list of attributes (name=>$value) to be updated - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows being updated - */ - public function updateByPk($pk,$attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.updateByPk()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $table=$this->getTableSchema(); - $criteria=$builder->createPkCriteria($table,$pk,$condition,$params); - $this->applyScopes($criteria); - $command=$builder->createUpdateCommand($table,$attributes,$criteria); - return $command->execute(); - } - - /** - * Updates records with the specified condition. - * See {@link find()} for detailed explanation about $condition and $params. - * Note, the attributes are not checked for safety and no validation is done. - * @param array list of attributes (name=>$value) to be updated - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows being updated - */ - public function updateAll($attributes,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.updateAll()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createCriteria($condition,$params); - $this->applyScopes($criteria); - $command=$builder->createUpdateCommand($this->getTableSchema(),$attributes,$criteria); - return $command->execute(); - } - - /** - * Updates one or several counter columns. - * Note, this updates all rows of data unless a condition or criteria is specified. - * See {@link find()} for detailed explanation about $condition and $params. - * @param array the counters to be updated (column name=>increment value) - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows being updated - */ - public function updateCounters($counters,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.updateCounters()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createCriteria($condition,$params); - $this->applyScopes($criteria); - $command=$builder->createUpdateCounterCommand($this->getTableSchema(),$counters,$criteria); - return $command->execute(); - } - - /** - * Deletes rows with the specified primary key. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value). - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows deleted - */ - public function deleteByPk($pk,$condition='',$params=array()) - { - Prado::trace(get_class($this).'.deleteByPk()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createPkCriteria($this->getTableSchema(),$pk,$condition,$params); - $this->applyScopes($criteria); - $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria); - return $command->execute(); - } - - /** - * Deletes rows with the specified condition. - * See {@link find()} for detailed explanation about $condition and $params. - * @param mixed query condition or criteria. - * @param array parameters to be bound to an SQL statement. - * @return integer the number of rows deleted - */ - public function deleteAll($condition='',$params=array()) - { - Prado::trace(get_class($this).'.deleteAll()','System.Testing.Data.ActiveRecord.TActiveRecord'); - $builder=$this->getCommandBuilder(); - $criteria=$builder->createCriteria($condition,$params); - $this->applyScopes($criteria); - $command=$builder->createDeleteCommand($this->getTableSchema(),$criteria); - return $command->execute(); - } - - /** - * Creates an active record with the given attributes. - * This method is internally used by the find methods. - * @param array attribute values (column name=>column value) - * @param boolean whether to call {@link afterFind} after the record is populated. - * This parameter is added in version 1.0.3. - * @return CActiveRecord the newly created active record. The class of the object is the same as the model class. - * Null is returned if the input data is false. - */ - public function populateRecord($attributes,$callAfterFind=true) - { - if($attributes!==false) - { - $record=$this->instantiate($attributes); - $record->_md=$this->getMetaData(); - foreach($attributes as $name=>$value) - { - if(property_exists($record,$name)) - $record->$name=$value; - else if(isset($record->_md->columns[$name])) - $record->_attributes[$name]=$value; - } - $record->attachBehaviors($record->behaviors()); - if($callAfterFind) - $record->afterFind(); - return $record; - } - else - return null; - } - - /** - * Creates a list of active records based on the input data. - * This method is internally used by the find methods. - * @param array list of attribute values for the active records. - * @param boolean whether to call {@link afterFind} after each record is populated. - * This parameter is added in version 1.0.3. - * @return array list of active records. - */ - public function populateRecords($data,$callAfterFind=true) - { - $records=array(); - $md=$this->getMetaData(); - $table=$md->tableSchema; - foreach($data as $attributes) - { - $record=$this->instantiate($attributes); - $record->_md=$md; - foreach($attributes as $name=>$value) - { - if(property_exists($record,$name)) - $record->$name=$value; - else if(isset($record->_md->columns[$name])) - $record->_attributes[$name]=$value; - } - $record->attachBehaviors($record->behaviors()); - if($callAfterFind) - $record->afterFind(); - $records[]=$record; - } - return $records; - } - - /** - * Creates an active record instance. - * This method is called by {@link populateRecord} and {@link populateRecords}. - * You may override this method if the instance being created - * depends the attributes that are to be populated to the record. - * For example, by creating a record based on the value of a column, - * you may implement the so-called single-table inheritance mapping. - * @param array list of attribute values for the active records. - * @return CActiveRecord the active record - * @since 1.0.2 - */ - protected function instantiate($attributes) - { - $class=get_class($this); - $model=new $class(null); - $model->setScenario('update'); - return $model; - } - - /** - * Returns whether there is an element at the specified offset. - * This method is required by the interface ArrayAccess. - * @param mixed the offset to check on - * @return boolean - * @since 1.0.2 - */ - public function offsetExists($offset) - { - return isset($this->getMetaData()->columns[$offset]); - } -} - - -/** - * CBaseActiveRelation is the base class for all active relations. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0.4 - */ -class TBaseActiveRelation extends TComponent -{ - /** - * @var string name of the related object - */ - public $name; - /** - * @var string name of the related active record class - */ - public $className; - /** - * @var string the foreign key in this relation - */ - public $foreignKey; - /** - * @var mixed list of column names (an array, or a string of names separated by commas) to be selected. - * Do not quote or prefix the column names unless they are used in an expression. - * In that case, you should prefix the column names with '??.'. - */ - public $select='*'; - /** - * @var string WHERE clause. For {@link CActiveRelation} descendant classes, column names - * referenced in the condition should be disambiguated with prefix '??.'. - */ - public $condition=''; - /** - * @var array the parameters that are to be bound to the condition. - * The keys are parameter placeholder names, and the values are parameter values. - */ - public $params=array(); - /** - * @var string GROUP BY clause. For {@link CActiveRelation} descendant classes, column names - * referenced in this property should be disambiguated with prefix '??.'. - */ - public $group=''; - /** - * @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names - * referenced in this property should be disambiguated with prefix '??.'. - */ - public $having=''; - /** - * @var string ORDER BY clause. For {@link CActiveRelation} descendant classes, column names - * referenced in this property should be disambiguated with prefix '??.'. - */ - public $order=''; - - /** - * Constructor. - * @param string name of the relation - * @param string name of the related active record class - * @param string foreign key for this relation - * @param array additional options (name=>value). The keys must be the property names of this class. - */ - public function __construct($name,$className,$foreignKey,$options=array()) - { - $this->name=$name; - $this->className=$className; - $this->foreignKey=$foreignKey; - foreach($options as $name=>$value) - $this->$name=$value; - } - - /** - * Merges this relation with a criteria specified dynamically. - * @param array the dynamically specified criteria - * @since 1.0.5 - */ - public function mergeWith($criteria) - { - if(isset($criteria['select']) && $this->select!==$criteria['select']) - { - if($this->select==='*') - $this->select=$criteria['select']; - else if($criteria['select']!=='*') - { - $select1=is_string($this->select)?preg_split('/\s*,\s*/',trim($this->select),-1,PREG_SPLIT_NO_EMPTY):$this->select; - $select2=is_string($criteria['select'])?preg_split('/\s*,\s*/',trim($criteria['select']),-1,PREG_SPLIT_NO_EMPTY):$criteria['select']; - $this->select=array_merge($select1,array_diff($select2,$select1)); - } - } - - if(isset($criteria['condition']) && $this->condition!==$criteria['condition']) - { - if($this->condition==='') - $this->condition=$criteria['condition']; - else if($criteria['condition']!=='') - $this->condition="({$this->condition}) AND ({$criteria['condition']})"; - } - - if(isset($criteria['params']) && $this->params!==$criteria['params']) - $this->params=array_merge($this->params,$criteria['params']); - - if(isset($criteria['order']) && $this->order!==$criteria['order']) - { - if($this->order==='') - $this->order=$criteria['order']; - else if($criteria['order']!=='') - $this->order.=', '.$criteria['order']; - } - - if(isset($criteria['group']) && $this->group!==$criteria['group']) - { - if($this->group==='') - $this->group=$criteria['group']; - else if($criteria['group']!=='') - $this->group.=', '.$criteria['group']; - } - - if(isset($criteria['having']) && $this->having!==$criteria['having']) - { - if($this->having==='') - $this->having=$criteria['having']; - else if($criteria['having']!=='') - $this->having="({$this->having}) AND ({$criteria['having']})"; - } - } -} - - -/** - * CStatRelation represents a statistical relational query. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0.4 - */ -class TStatRelation extends TBaseActiveRelation -{ - /** - * @var string the statistical expression. Defaults to 'COUNT(*)', meaning - * the count of child objects. - */ - public $select='COUNT(*)'; - /** - * @var mixed the default value to be assigned to those records that do not - * receive a statistical query result. Defaults to 0. - */ - public $defaultValue=0; - - /** - * Merges this relation with a criteria specified dynamically. - * @param array the dynamically specified criteria - * @since 1.0.5 - */ - public function mergeWith($criteria) - { - parent::mergeWith($criteria); - - if(isset($criteria['defaultValue'])) - $this->defaultValue=$criteria['defaultValue']; - } -} - - -/** - * CActiveRelation is the base class for representing active relations that bring back related objects. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TActiveRelation extends TBaseActiveRelation -{ - /** - * @var string join type. Defaults to 'LEFT OUTER JOIN'. - */ - public $joinType='LEFT OUTER JOIN'; - /** - * @var string ON clause. The condition specified here will be appended to the joining condition using AND operator. - * @since 1.0.2 - */ - public $on=''; - /** - * @var string the alias for the table that this relation refers to. Defaults to null, meaning - * the alias will be the same as the relation name. - * @since 1.0.1 - */ - public $alias; - /** - * @var string|array specifies which related objects should be eagerly loaded when this related object is lazily loaded. - * For more details about this property, see {@link CActiveRecord::with()}. - */ - public $with=array(); - - /** - * Merges this relation with a criteria specified dynamically. - * @param array the dynamically specified criteria - * @since 1.0.5 - */ - public function mergeWith($criteria) - { - if(isset($criteria['condition']) && $this->on!==$criteria['condition']) - { - if($this->on==='') - $this->on=$criteria['condition']; - else if($criteria['condition']!=='') - $this->on="({$this->on}) AND ({$criteria['condition']})"; - } - unset($criteria['condition']); - - parent::mergeWith($criteria); - - if(isset($criteria['joinType'])) - $this->joinType=$criteria['joinType']; - - if(isset($criteria['on']) && $this->on!==$criteria['on']) - { - if($this->on==='') - $this->on=$criteria['on']; - else if($criteria['on']!=='') - $this->on="({$this->on}) AND ({$criteria['on']})"; - } - - if(isset($criteria['with'])) - $this->with=$criteria['with']; - - if(isset($criteria['alias'])) - $this->alias=$criteria['alias']; - - if(isset($criteria['together'])) - $this->together=$criteria['together']; - } -} - - -/** - * CBelongsToRelation represents the parameters specifying a BELONGS_TO relation. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TBelongsToRelation extends TActiveRelation -{ -} - - -/** - * CHasOneRelation represents the parameters specifying a HAS_ONE relation. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class THasOneRelation extends TActiveRelation -{ -} - - -/** - * CHasManyRelation represents the parameters specifying a HAS_MANY relation. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class THasManyRelation extends TActiveRelation -{ - /** - * @var integer limit of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no limit. - */ - public $limit=-1; - /** - * @var integer offset of the rows to be selected. It is effective only for lazy loading this related object. Defaults to -1, meaning no offset. - */ - public $offset=-1; - /** - * @var boolean whether this table should be joined with the primary table. - * When setting this property to be false, the table associated with this relation will - * appear in a separate JOIN statement. Defaults to true, meaning the table will be joined - * together with the primary table. Note that in version 1.0.x, the default value of this property was false. - */ - public $together=true; - - /** - * Merges this relation with a criteria specified dynamically. - * @param array the dynamically specified criteria - * @since 1.0.5 - */ - public function mergeWith($criteria) - { - parent::mergeWith($criteria); - if(isset($criteria['limit']) && $criteria['limit']>0) - $this->limit=$criteria['limit']; - - if(isset($criteria['offset']) && $criteria['offset']>=0) - $this->offset=$criteria['offset']; - } -} - - -/** - * CManyManyRelation represents the parameters specifying a MANY_MANY relation. - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TManyManyRelation extends THasManyRelation -{ -} - - -/** - * CActiveRecordMetaData represents the meta-data for an Active Record class. - * - * @author Qiang Xue - * @version $Id: CActiveRecord.php 1127 2009-06-13 20:26:35Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0 - */ -class TActiveRecordMetaData -{ - /** - * @var CDbTableSchema the table schema information - */ - public $tableSchema; - /** - * @var array table columns - */ - public $columns; - /** - * @var array list of relations - */ - public $relations=array(); - /** - * @var array attribute default values - */ - public $attributeDefaults=array(); - - private $_model; - - /** - * Constructor. - * @param CActiveRecord the model instance - */ - public function __construct($model) - { - $this->_model=$model; - - $tableName=$model->tableName(); - if(($table=$model->getDbConnection()->getSchema()->getTable($tableName))===null) - throw new TDbException('The table "'.$tableName.'" for active record class "'.get_class($model).'" cannot be found in the database.'); - - if($table->primaryKey===null) - $table->primaryKey=$model->primaryKey(); - $this->tableSchema=$table; - $this->columns=$table->columns; - - foreach($table->columns as $name=>$column) - { - if(!$column->isPrimaryKey && $column->defaultValue!==null) - $this->attributeDefaults[$name]=$column->defaultValue; - } - - foreach($model->relations() as $name=>$config) - { - if(isset($config[0],$config[1],$config[2])) // relation class, AR class, FK - $this->relations[$name]=new $config[0]($name,$config[1],$config[2],array_slice($config,3)); - else - throw new TDbException('Active record "'.get_class($model).'" has an invalid configuration for relation "'.$name.'". It must specify the relation type, the related active record class and the foreign key.'); - } - } -} - -/** - * TActiveRecordInvalidFinderResult class. - * TActiveRecordInvalidFinderResult defines the enumerable type for possible results - * if an invalid {@link TActiveRecord::__call magic-finder} invoked. - * - * The following enumerable values are defined: - * - Null: return null (default) - * - Exception: throws a TActiveRecordException - * - * @author Yves Berkholz - * @version $Id: TActiveRecord.php 2649 2009-05-11 07:44:55Z godzilla80@gmx.net $ - * @package System.Data.ActiveRecord - * @see TActiveRecordManager::setInvalidFinderResult - * @see TActiveRecordConfig::setInvalidFinderResult - * @see TActiveRecord::setInvalidFinderResult - * @since 3.1.5 - */ -class TActiveRecordInvalidFinderResult extends TEnumerable -{ - const Null = 'Null'; - const Exception = 'Exception'; -} diff --git a/framework/Testing/Data/ActiveRecord/TActiveRecordBehavior.php b/framework/Testing/Data/ActiveRecord/TActiveRecordBehavior.php deleted file mode 100644 index cd4d8386..00000000 --- a/framework/Testing/Data/ActiveRecord/TActiveRecordBehavior.php +++ /dev/null @@ -1,94 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2009 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * CActiveRecordBehavior is the base class for behaviors that can be attached to {@link CActiveRecord}. - * Compared with {@link CModelBehavior}, CActiveRecordBehavior attaches to more events - * that are only defined by {@link CActiveRecord}. - * - * @author Qiang Xue - * @version $Id: CActiveRecordBehavior.php 564 2009-01-21 22:07:10Z qiang.xue $ - * @package System.Testing.Data.ActiveRecord - * @since 1.0.2 - */ -class TActiveRecordBehavior extends TModelBehavior -{ - /** - * Declares events and the corresponding event handler methods. - * If you override this method, make sure you merge the parent result to the return value. - * @return array events (array keys) and the corresponding event handler methods (array values). - * @see CBehavior::events - */ - public function events() - { - return array_merge(parent::events(), array( - 'onBeforeSave'=>'beforeSave', - 'onAfterSave'=>'afterSave', - 'onBeforeDelete'=>'beforeDelete', - 'onAfterDelete'=>'afterDelete', - 'onAfterConstruct'=>'afterConstruct', - 'onAfterFind'=>'afterFind', - )); - } - - /** - * Responds to {@link CActiveRecord::onBeforeSave} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function beforeSave($event) - { - } - - /** - * Responds to {@link CActiveRecord::onAfterSave} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function afterSave($event) - { - } - - /** - * Responds to {@link CActiveRecord::onBeforeDelete} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function beforeDelete($event) - { - } - - /** - * Responds to {@link CActiveRecord::onAfterDelete} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function afterDelete($event) - { - } - - /** - * Responds to {@link CActiveRecord::onAfterConstruct} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function afterConstruct($event) - { - } - - /** - * Responds to {@link CActiveRecord::onAfterFind} event. - * Overrides this method if you want to handle the corresponding event of the {@link CBehavior::owner owner}. - * @param CEvent event parameter - */ - public function afterFind($event) - { - } -} diff --git a/framework/Testing/Data/ActiveRecord/TActiveRecordCriteria.php b/framework/Testing/Data/ActiveRecord/TActiveRecordCriteria.php deleted file mode 100644 index 46eddaf9..00000000 --- a/framework/Testing/Data/ActiveRecord/TActiveRecordCriteria.php +++ /dev/null @@ -1,254 +0,0 @@ - - */ - -Prado::using('System.Testing.Data.Schema.TDbCriteria'); - -class TSqlCriteria extends TDbCriteria -{ - - private $_TDbCriteria = null; - - public function __construct($condition=null, $parameters=array()) - { - if(!is_array($parameters) && func_num_args() > 1) - $parameters = array_slice(func_get_args(), 1); - - $this->_TDbCriteria = $this; - - $this->_TDbCriteria->params = new ArrayObject(); - - $param = array( - 'condition' => ($condition !== null) ? $condition : '', - 'params' => $parameters - ); - } - - public function getCondition() - { - return $this->_TDbCriteria->condition; - } - - public function setCondition($value) - { - if(empty($value)) - return; - - // supporting the following SELECT-syntax: - // [ORDER BY {col_name | expr | position} - // [ASC | DESC], ...] - // [LIMIT {[offset,] row_count | row_count OFFSET offset}] - // See: http://dev.mysql.com/doc/refman/5.0/en/select.html - - - if(preg_match('/ORDER\s+BY\s+(.*?)(?=LIMIT)|ORDER\s+BY\s+(.*?)$/i', $value, $matches) > 0) { - // condition contains ORDER BY - $value = str_replace($matches[0], '', $value); - if(strlen($matches[1]) > 0) { - $this->_TDbCriteria->setOrdersBy($matches[1]); - } else if(strlen($matches[2]) > 0) { - $this->setOrdersBy($matches[2]); - } - } - - if(preg_match('/LIMIT\s+([\d\s,]+)/i', $value, $matches) > 0) { - // condition contains limit - $value = str_replace($matches[0], '', $value); - // remove limit from query - if(strpos($matches[1], ',')) { // both offset and limit given - list($offset, $limit) = explode(',', $matches[1]); - $this->_TDbCriteria->limit = (int)$limit; - $this->_TDbCriteria->offset = (int)$offset; - } else { // only limit given - $this->_TDbCriteria->limit = (int)$matches[1]; - } - } - - if(preg_match('/OFFSET\s+(\d+)/i', $value, $matches) > 0) { - // condition contains offset - $value = str_replace($matches[0], '', $value); - // remove offset from query - $this->_TDbCriteria->offset = (int)$matches[1]; // set offset in criteria - } - - $this->_TDbCriteria->condition = trim($value); - } - - public function getParameters() - { - return $this->_TDbCriteria->params; - } - - public function setParameters($value) - { - if(is_array($value)) - { - $this->_TDbCriteria->params = $value; - } - elseif ($value instanceof ArrayAccess) - { - $this->_TDbCriteria->params = (array)$value; - } - else - throw new TException('Value must be an array or of type ArrayAccess.'); - } - - public function getIsNamedParameters() - { - foreach($this->_TDbCriteria->params as $key=>$val) - return is_string($key); - } - - public function getLimit() - { - return ($this->_TDbCriteria->limit != -1) ? $this->_TDbCriteria->limit : null; - } - - public function setLimit($value) - { - $this->_TDbCriteria->limit = (int)$value; - } - - public function getOffset() - { - return ($this->_TDbCriteria->offset != -1) ? $this->_TDbCriteria->offset : null; - } - - public function setOffset($value) - { - $this->_TDbCriteria->offset = (int)$value; - } - - public function getOrdersBy() - { - if(empty($this->_TDbCriteria->order)) - return array(); - - $value=trim(preg_replace('/\s+/',' ', $this->_TDbCriteria->order)); - $orderBys=array(); - foreach(explode(',',$value) as $orderBy) - { - $vs=explode(' ',trim($orderBy)); - - $orderBys[$vs[0]]=isset($vs[1])?$vs[1]:'asc'; - } - return $orderBys; - } - - public function setOrdersBy($value) - { - if(is_string($value)) - $this->TDbCriteria->order = $value; - elseif (is_array($value) || $value instanceof Traversable) - { - $str = ''; - foreach($value as $key => $val) - $str .= (($str != '') ? ', ': '').$key.' '.$val; - - $this->_TDbCriteria->order = $str; - } - } - - public function __toString() - { - $str = ''; - if(strlen((string)$this->getCondition()) > 0) - $str .= '"'.(string)$this->getCondition().'"'; - $params = array(); - foreach($this->getParameters() as $k=>$v) - $params[] = "{$k} => ${v}"; - if(count($params) > 0) - $str .= ', "'.implode(', ',$params).'"'; - $orders = array(); - foreach($this->getOrdersBy() as $k=>$v) - $orders[] = "{$k} => ${v}"; - if(count($orders) > 0) - $str .= ', "'.implode(', ',$orders).'"'; - if($this->getLimit() !==null) - $str .= ', '.$this->_limit; - if($this->getOffset() !== null) - $str .= ', '.$this->_offset; - return $str; - } - - /** - * Returns a property value or an event handler list by property or event name. - * Do not call this method. This is a PHP magic method that we override - * to allow using the following syntax to read a property: - * - * $value=$component->PropertyName; - * - * and to obtain the event handler list for an event, - * - * $eventHandlerList=$component->EventName; - * - * @param string the property name or the event name - * @return mixed the property value or the event handler list - * @throws TInvalidOperationException if the property/event is not defined. - */ - public function __get($name) - { - $getter='get'.$name; - if(method_exists($this,$getter)) - { - // getting a property - return $this->$getter(); - } - else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) - { - // getting an event (handler list) - $name=strtolower($name); - if(!isset($this->_e[$name])) - $this->_e[$name]=new TList; - return $this->_e[$name]; - } - else - { - throw new TInvalidOperationException('component_property_undefined',get_class($this),$name); - } - } - - /** - * Sets value of a component property. - * Do not call this method. This is a PHP magic method that we override - * to allow using the following syntax to set a property or attach an event handler. - * - * $this->PropertyName=$value; - * $this->EventName=$handler; - * - * @param string the property name or event name - * @param mixed the property value or event handler - * @throws TInvalidOperationException If the property is not defined or read-only. - */ - public function __set($name,$value) - { - $setter='set'.$name; - if(method_exists($this,$setter)) - { - $this->$setter($value); - } - else if(strncasecmp($name,'on',2)===0 && method_exists($this,$name)) - { - $this->attachEventHandler($name,$value); - } - else if(method_exists($this,'get'.$name)) - { - throw new TInvalidOperationException('component_property_readonly',get_class($this),$name); - } - else - { - throw new TInvalidOperationException('component_property_undefined',get_class($this),$name); - } - } - -} - -class TActiveRecordCriteria extends TSqlCriteria -{ - -} -- cgit v1.2.3