From 8708f7e88e423b44ea4b3b8fff14f055d2b5c4ac Mon Sep 17 00:00:00 2001 From: wei <> Date: Fri, 4 May 2007 08:37:12 +0000 Subject: Add saving/updating ActiveRecord relationships. --- .../Relations/TActiveRecordBelongsTo.php | 24 ++++ .../Relations/TActiveRecordHasMany.php | 27 +++++ .../Relations/TActiveRecordHasManyAssociation.php | 132 +++++++++++++++++++-- .../ActiveRecord/Relations/TActiveRecordHasOne.php | 19 +++ .../Relations/TActiveRecordRelation.php | 8 +- .../Relations/TActiveRecordRelationContext.php | 59 +++++++-- 6 files changed, 250 insertions(+), 19 deletions(-) (limited to 'framework/Data/ActiveRecord/Relations') diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php index 1168bf55..9374af64 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php @@ -104,6 +104,30 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation $source->{$prop} = $collections[$hash][0]; } } + + /** + * Updates the source object first. + * @return boolean true if all update are success (including if no update was required), false otherwise . + */ + public function updateAssociatedRecords() + { + $obj = $this->getContext()->getSourceRecord(); + $fkObject = $obj->{$this->getContext()->getProperty()}; + $registry = $fkObject->getRecordManager()->getObjectStateRegistry(); + if($registry->shouldPersistObject($fkObject)) + { + if($fkObject!==null) + { + $fkObject->save(); + $source = $this->getSourceRecord(); + $fkeys = $this->findForeignKeys($source, $fkObject); + foreach($fkeys as $srcKey => $fKey) + $source->{$srcKey} = $fkObject->{$fKey}; + return true; + } + } + return true; + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php index ab28a455..d70f911b 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php @@ -85,6 +85,33 @@ class TActiveRecordHasMany extends TActiveRecordRelation $fkObjects = $this->findForeignObjects($fields,$indexValues); $this->populateResult($results,$properties,$fkObjects,$fields); } + + /** + * Updates the associated foreign objects. + * @return boolean true if all update are success (including if no update was required), false otherwise . + */ + public function updateAssociatedRecords() + { + $obj = $this->getContext()->getSourceRecord(); + $fkObjects = &$obj->{$this->getContext()->getProperty()}; + $success=true; + if(($total = count($fkObjects))> 0) + { + $source = $this->getSourceRecord(); + $registry = $source->getRecordManager()->getObjectStateRegistry(); + $fkeys = $this->findForeignKeys($fkObjects[0], $source); + for($i=0;$i<$total;$i++) + { + if($registry->shouldPersistObject($fkObjects[$i])) + { + foreach($fkeys as $fKey => $srcKey) + $fkObjects[$i]->{$fKey} = $source->{$srcKey}; + $success = $success && $fkObjects[$i]->save(); + } + } + } + return $success; + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php index 50558a2b..2dee2bcd 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php @@ -87,6 +87,7 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation private $_association; private $_sourceTable; private $_foreignTable; + private $_association_columns=array(); /** * Get the foreign key index values from the results and make calls to the @@ -117,8 +118,14 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation { $gateway = $this->getSourceRecord()->getRecordGateway(); $conn = $this->getSourceRecord()->getDbConnection(); - $table = $this->getContext()->getAssociationTable(); - $this->_association = $gateway->getTableInfo($conn, $table); + //table name may include the fk column name separated with a dot. + $table = explode('.', $this->getContext()->getAssociationTable()); + if(count($table)>1) + { + $columns = preg_replace('/^\((.*)\)/', '\1', $table[1]); + $this->_association_columns = preg_split('/\s*[, ]\*/',$columns); + } + $this->_association = $gateway->getTableInfo($conn, $table[0]); } return $this->_association; } @@ -158,6 +165,13 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation return $this->getSourceRecord()->getRecordGateway()->getCommand($this->getSourceRecord()); } + protected function getForeignCommandBuilder() + { + $obj = $this->getContext()->getForeignRecordFinder(); + return $this->getSourceRecord()->getRecordGateway()->getCommand($obj); + } + + /** * Fetches the foreign objects using TActiveRecord::findAllByIndex() * @param array field names @@ -172,12 +186,12 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $command = $this->createCommand($criteria, $foreignKeys,$indexValues,$sourceKeys); $srcProps = array_keys($sourceKeys); $collections=array(); - foreach($command->query() as $row) + foreach($this->getCommandBuilder()->onExecuteCommand($command, $command->query()) as $row) { $hash = $this->getObjectHash($row, $srcProps); foreach($srcProps as $column) unset($row[$column]); - $obj = new $type($row); + $obj = $this->createFkObject($type,$row,$foreignKeys); $collections[$hash][] = $obj; $registry->registerClean($obj); } @@ -185,6 +199,24 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $this->setResultCollection($results, $collections, array_values($sourceKeys)); } + /** + * @param string active record class name. + * @param array row data + * @param array foreign key column names + * @return TActiveRecord + */ + protected function createFkObject($type,$row,$foreignKeys) + { + $obj = new $type($row); + if(count($this->_association_columns) > 0) + { + $i=0; + foreach($foreignKeys as $ref=>$fk) + $obj->{$ref} = $row[$this->_association_columns[$i++]]; + } + return $obj; + } + /** * @param TSqlCriteria * @param TTableInfo association table info @@ -205,7 +237,7 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $limit = $criteria->getLimit(); $offset = $criteria->getOffset(); - $builder = $this->getCommandBuilder()->getBuilder(); + $builder = $this->getForeignCommandBuilder()->getBuilder(); $command = $builder->applyCriterias($sql,$parameters,$ordering,$limit,$offset); $this->getCommandBuilder()->onCreateCommand($command, $criteria); return $command; @@ -220,7 +252,8 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $columns=array(); $table = $this->getAssociationTable(); $tableName = $table->getTableFullName(); - foreach($sourceKeys as $name=>$fkName) + $columnNames = array_merge(array_keys($sourceKeys),$this->_association_columns); + foreach($columnNames as $name) $columns[] = $tableName.'.'.$table->getColumn($name)->getColumnName(); return implode(', ', $columns); } @@ -241,9 +274,14 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $fkTable = $fkInfo->getTableFullName(); $joins = array(); + $hasAssociationColumns = count($this->_association_columns) > 0; + $i=0; foreach($foreignKeys as $ref=>$fk) { - $refField = $refInfo->getColumn($ref)->getColumnName(); + if($hasAssociationColumns) + $refField = $refInfo->getColumn($this->_association_columns[$i++])->getColumnName(); + else + $refField = $refInfo->getColumn($ref)->getColumnName(); $fkField = $fkInfo->getColumn($fk)->getColumnName(); $joins[] = "{$fkTable}.{$fkField} = {$refTable}.{$refField}"; } @@ -251,5 +289,85 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $index = $this->getCommandBuilder()->getIndexKeyCondition($refInfo,array_keys($sourceKeys), $indexValues); return "INNER JOIN {$refTable} ON ({$joinCondition}) AND {$index}"; } + + /** + * Updates the associated foreign objects. + * @return boolean true if all update are success (including if no update was required), false otherwise . + */ + public function updateAssociatedRecords() + { + $obj = $this->getContext()->getSourceRecord(); + $fkObjects = &$obj->{$this->getContext()->getProperty()}; + $success=true; + if(($total = count($fkObjects))> 0) + { + $source = $this->getSourceRecord(); + $registry = $source->getRecordManager()->getObjectStateRegistry(); + $builder = $this->getAssociationTableCommandBuilder(); + for($i=0;$i<$total;$i++) + { + if($registry->shouldPersistObject($fkObjects[$i])) + $success = $success && $fkObjects[$i]->save(); + } + return $this->updateAssociationTable($obj, $fkObjects, $builder) && $success; + } + return $success; + } + + protected function getAssociationTableCommandBuilder() + { + $conn = $this->getContext()->getSourceRecord()->getDbConnection(); + return $this->getAssociationTable()->createCommandBuilder($conn); + } + + protected function hasAssociationData($builder,$data) + { + $condition=array(); + $table = $this->getAssociationTable(); + foreach($data as $name=>$value) + $condition[] = $table->getColumn($name)->getColumnName().' = ?'; + $command = $builder->createCountCommand(implode(' AND ', $condition),array_values($data)); + $result = $this->getCommandBuilder()->onExecuteCommand($command, intval($command->queryScalar())); + return intval($result) > 0; + } + + protected function addAssociationData($builder,$data) + { + $command = $builder->createInsertCommand($data); + return $this->getCommandBuilder()->onExecuteCommand($command, $command->execute()) > 0; + } + + protected function updateAssociationTable($obj,$fkObjects, $builder) + { + $source = $this->getSourceRecordValues($obj); + $foreignKeys = $this->findForeignKeys($this->getAssociationTable(), $fkObjects[0]); + $success=true; + foreach($fkObjects as $fkObject) + { + $data = array_merge($source, $this->getForeignObjectValues($foreignKeys,$fkObject)); + if(!$this->hasAssociationData($builder,$data)) + $success = $this->addAssociationData($builder,$data) && $success; + } + return $success; + } + + protected function getSourceRecordValues($obj) + { + $sourceKeys = $this->findForeignKeys($this->getAssociationTable(), $obj); + $indexValues = $this->getIndexValues(array_values($sourceKeys), $obj); + $data = array(); + $i=0; + foreach($sourceKeys as $name=>$srcKey) + $data[$name] = $indexValues[0][$i++]; + return $data; + } + + protected function getForeignObjectValues($foreignKeys,$fkObject) + { + $data=array(); + foreach($foreignKeys as $name=>$fKey) + $data[$name] = $fkObject->{$fKey}; + return $data; + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php index 6348f16b..7127d2ac 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php @@ -119,6 +119,25 @@ class TActiveRecordHasOne extends TActiveRecordRelation $source->{$prop} = $collections[$hash][0]; } } + + /** + * Updates the associated foreign objects. + * @return boolean true if all update are success (including if no update was required), false otherwise . + */ + public function updateAssociatedRecords() + { + $fkObject = $this->getContext()->getPropertyValue(); + $registry = $fkObject->getRecordManager()->getObjectStateRegistry(); + if($registry->shouldPersistObject($fkObject)) + { + $source = $this->getSourceRecord(); + $fkeys = $this->findForeignKeys($fkObject, $source); + foreach($fkeys as $fKey => $srcKey) + $fkObject->{$fKey} = $source->{$srcKey}; + return $fkObject->save(); + } + return true; + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php index 46609095..4dc9743f 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php @@ -95,7 +95,9 @@ abstract class TActiveRecordRelation if($fkeys['table']===$matchingTableName) return $fkeys['keys']; } - throw new TActiveRecordException('no fk defined for '.$tableInfo->getTableFullName()); + $matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName(); + throw new TActiveRecordException('ar_relations_missing_fk', + $tableInfo->getTableFullName(), $matching); } /** @@ -189,6 +191,10 @@ abstract class TActiveRecordRelation $prop = $this->getContext()->getProperty(); $source->{$prop} = isset($collections[$hash]) ? $collections[$hash] : array(); } + + public function updateAssociatedRecords() + { + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php index 167c90a5..e601eb53 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php @@ -35,38 +35,52 @@ class TActiveRecordRelationContext private $_criteria; private $_relation; - public function __construct($source, $property, $criteria) + public function __construct($source, $property=null, $criteria=null) { $this->_sourceRecord=$source; - $this->_property=$property; $this->_criteria=$criteria; - $this->_relation = $this->getSourceRecordRelation($property); + if($property!==null) + list($this->_property, $this->_relation) = $this->getSourceRecordRelation($property); } /** * Uses ReflectionClass to obtain the relation details array of a given * property from the $RELATIONS static property in TActiveRecord. * @param string relation property name - * @return array relation definition. + * @return array array($propertyName, $relation) relation definition. * @throws TActiveRecordException if property is not defined or missing. */ protected function getSourceRecordRelation($property) { - $class = new ReflectionClass($this->_sourceRecord); - $statics = $class->getStaticProperties(); - if(!isset($statics[self::RELATIONS_CONST])) - throw new TActiveRecordException('ar_relations_undefined', - get_class($this->_sourceRecord), self::RELATIONS_CONST); $property = strtolower($property); - foreach($statics[self::RELATIONS_CONST] as $name => $relation) + foreach($this->getRecordRelationships() as $name => $relation) { if(strtolower($name)===$property) - return $relation; + return array($name, $relation); } throw new TActiveRecordException('ar_undefined_relation_prop', $property, get_class($this->_sourceRecord), self::RELATIONS_CONST); } + /** + * @return array the key and values of TActiveRecord::$RELATIONS + */ + public function getRecordRelationships() + { + $class = new ReflectionClass($this->_sourceRecord); + $statics = $class->getStaticProperties(); + if(isset($statics[self::RELATIONS_CONST])) + return $statics[self::RELATIONS_CONST]; + else + return array(); + } + + public function getPropertyValue() + { + $obj = $this->getSourceRecord(); + return $obj->{$this->getProperty()}; + } + /** * @return string name of the record property that the relationship results will be assigned to. */ @@ -162,6 +176,29 @@ class TActiveRecordRelationContext throw new TActiveRecordException('ar_invalid_relationship'); } } + + /** + * @return TActiveRecordRelationCommand + */ + public function updateAssociatedRecords($updateBelongsTo=false) + { + Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationCommand'); + $success=true; + foreach($this->getRecordRelationships() as $property=>$relation) + { + $belongsTo = $relation[0]==TActiveRecord::BELONGS_TO; + if(($updateBelongsTo && $belongsTo) || (!$updateBelongsTo && !$belongsTo)) + { + $obj = $this->getSourceRecord(); + if(!empty($obj->{$property})) + { + $context = new self($this->getSourceRecord(),$property); + $success = $success && $context->getRelationHandler()->updateAssociatedRecords(); + } + } + } + return $success; + } } ?> \ No newline at end of file -- cgit v1.2.3