From 199fc1254f84f851a2894df94487a45ed68f7c98 Mon Sep 17 00:00:00 2001 From: xue <> Date: Fri, 28 Sep 2007 18:08:20 +0000 Subject: Fixed #665. --- HISTORY | 1 + .../protected/pages/Database/ActiveRecord.page | 57 +++++++++++--- .../pages/GettingStarted/NewFeatures.page | 1 + framework/Data/ActiveRecord/TActiveRecord.php | 90 ++++++++++++++++++++-- .../Data/ActiveRecord/TActiveRecordGateway.php | 8 +- .../tests/ActiveDropDownListTestCase.php | 4 +- 6 files changed, 141 insertions(+), 20 deletions(-) diff --git a/HISTORY b/HISTORY index 6f9472ce..2448081a 100644 --- a/HISTORY +++ b/HISTORY @@ -13,6 +13,7 @@ ENH: Ticket#370 - TDataGrid now supports grouping cells (Qiang) ENH: Ticket#577 - Added image button support for TPager (Qiang) ENH: Ticket#623 - TMemCache to support multiple servers (Carl) ENH: Ticket#664 - Added support to styling thead, tbody, tfoot of TDataGrid (Qiang) +ENH: Ticket#665 - Added support to column mapping in Active Record (Qiang) ENH: Ticket#667 - Added TFeedService.ContentType property (Qiang) ENH: Ticket#672 - ForceSecureConnection to THttpRequest (Carl) ENH: Ticket#676 - Updated ActiveRecord Oracle Drives (Qiang) diff --git a/demos/quickstart/protected/pages/Database/ActiveRecord.page b/demos/quickstart/protected/pages/Database/ActiveRecord.page index 481985e9..a28c3d5e 100644 --- a/demos/quickstart/protected/pages/Database/ActiveRecord.page +++ b/demos/quickstart/protected/pages/Database/ActiveRecord.page @@ -52,21 +52,21 @@

Design Implications

-Prado's implementation of Active Record does not maintain referential identity. Each object obtained using -Active Record is a copy of the data in the database. For example, If you ask for -a particular customer and get back a Customer object, the next time you ask for +Prado's implementation of Active Record does not maintain referential identity. Each object obtained using +Active Record is a copy of the data in the database. For example, If you ask for +a particular customer and get back a Customer object, the next time you ask for that customer you get back another instance of a Customer object. This implies that a strict comparison (i.e., using ===) will return false, while loose comparison (i.e., using ==) will -return true if the object values are equal by loose comparison. +return true if the object values are equal by loose comparison.

This is design implication related to the following question. -"Do you think of the customer as an object, of which there's only one, +"Do you think of the customer as an object, of which there's only one, or do you think of the objects you operate on as copies of the database?" -Other O/R mappings will imply that there is only one Customer object with custID 100, and it literally is that customer. -If you get the customer and change a field on it, then you have now changed that customer. -"That constrasts with: you have changed this copy of the customer, but not that copy. -And if two people update the customer on two copies of the object, whoever updates first, +Other O/R mappings will imply that there is only one Customer object with custID 100, and it literally is that customer. +If you get the customer and change a field on it, then you have now changed that customer. +"That constrasts with: you have changed this copy of the customer, but not that copy. +And if two people update the customer on two copies of the object, whoever updates first, or maybe last, wins." [A. Hejlsberg 2003]

@@ -1059,6 +1059,45 @@ arrays. E.g. $player->skills[] = new SkillRecord(). If array w will be thrown.

+

Column Mapping

+

+Since v3.1.1, Active Record starts to support column mapping. Column mapping allows developers +to address columns in Active Record using a more consistent naming convention. In particular, +using column mapping, one can access a column using whatever name he likes, rather than limited by +the name defined in the database schema. +

+

+To use column mapping, declare a static array named COLUMN_MAPPING in the Active Record class. +The keys of the array are column names (called physical column names) as defined in the database +schema, while the values are corresponding property names (called logical column names) defined +in the Active Record class. The property names can be either public class member variable names or +component property names defined via getters/setters. If a physical column name happens to be the same +as the logical column name, they do not need to be listed in COLUMN_MAPPING. +

+ +class UserRecord extends TActiveRecord +{ + const TABLE='users'; + protected static $COLUMN_MAPPING=array + ( + 'user_id'=>'id', + 'email_address'=>'email', + 'first_name'=>'firstName', + 'last_name'=>'lastName', + ); + public $id; + public $username; // the physical and logical column names are the same + public $email; + public $firstName; + public $lastName; + //.... +} + +

+With the above column mapping, we can address first_name using $userRecord->firstName +instead of $userRecord->first_name. This helps separation of logic and model. +

+

References

Version 3.1.0

diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php index b7ccaa50..20463956 100644 --- a/framework/Data/ActiveRecord/TActiveRecord.php +++ b/framework/Data/ActiveRecord/TActiveRecord.php @@ -65,6 +65,28 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext'); * $user->save(); //update the 'admin' record. * * + * Since v3.1.1, TActiveRecord starts to support column mapping. The physical + * column names (defined in database) can be mapped to logical column names + * (defined in active classes as public properties.) To use this feature, declare + * a static class variable COLUMN_MAPPING like the following: + * + * class UserRecord extends TActiveRecord + * { + * const TABLE='users'; + * protected static $COLUMN_MAPPING=array + * ( + * 'user_id'=>'username', + * 'email_address'=>'email', + * ); + * public $username; + * pulbic $email; + * } + * + * In the above, the 'users' table consists of 'user_id' and 'email_address' columns, + * while the UserRecord class declares 'username' and 'email' properties. + * By using column mapping, we can regularize the naming convention of column names + * in active record. + * * @author Wei Zhuo * @version $Id$ * @package System.Data.ActiveRecord @@ -86,6 +108,17 @@ abstract class TActiveRecord extends TComponent */ private $_connection; + private static $_columnMapping=array(); + + /** + * This static variable defines the column mapping. + * The keys are physical column names as defined in database, + * and the values are logical column names as defined as public variable/property names + * for the corresponding active record class. + * @var array column mapping. Keys: physical column names, values: logical column names. + */ + protected static $COLUMN_MAPPING=array(); + /** * Prevent __call() method creating __sleep() when serializing. */ @@ -97,7 +130,10 @@ abstract class TActiveRecord extends TComponent /** * Prevent __call() method creating __wake() when unserializing. */ - public function __wake(){} + public function __wake() + { + $this->setupColumnMapping(); + } /** * Create a new instance of an active record with given $data. The record @@ -111,6 +147,20 @@ abstract class TActiveRecord extends TComponent $this->copyFrom($data); if($connection!==null) $this->_connection=$connection; + $this->setupColumnMapping(); + } + + /** + * @since 3.1.1 + */ + private function setupColumnMapping() + { + $className=get_class($this); + if(!isset(self::$_columnMapping[$className])) + { + $class=new ReflectionClass($className); + self::$_columnMapping[$className]=$class->getStaticPropertyValue('COLUMN_MAPPING'); + } } /** @@ -170,9 +220,9 @@ abstract class TActiveRecord extends TComponent foreach($properties as $prop) { if($strict) - $equals = $equals && $this->{$prop} === $record->{$prop}; + $equals = $equals && $this->getColumnValue($prop) === $record->getColumnValue($prop); else - $equals = $equals && $this->{$prop} == $record->{$prop}; + $equals = $equals && $this->getColumnValue($prop) == $record->getColumnValue($prop); if(!$equals) return false; } @@ -325,12 +375,12 @@ abstract class TActiveRecord extends TComponent $obj = Prado::createComponent($type); $tableInfo = $this->getRecordGateway()->getRecordTableInfo($obj); foreach($data as $name=>$value) - $obj->{$name} = $value; + $obj->setColumnValue($name,$value); /* foreach($tableInfo->getColumns()->getKeys() as $name) { if(isset($data[$name])) - $obj->{$name} = $data[$name]; + $obj->setColumnValue($name,$data[$name]); }*/ $obj->_readOnly = $tableInfo->getIsView(); $this->getRecordManager()->getObjectStateRegistry()->registerClean($obj); @@ -624,5 +674,35 @@ abstract class TActiveRecord extends TComponent { $this->raiseEvent('OnExecuteCommand', $this, $param); } + + /** + * Retrieves the column value according to column name. + * This method is used internally. + * @param string the column name (as defined in database schema) + * @return mixed the corresponding column value + * @since 3.1.1 + */ + public function getColumnValue($columnName) + { + $className=get_class($this); + if(isset(self::$_columnMapping[$className][$columnName])) + $columnName=self::$_columnMapping[$className][$columnName]; + return $this->$columnName; + } + + /** + * Sets the column value according to column name. + * This method is used internally. + * @param string the column name (as defined in database schema) + * @param mixed the corresponding column value + * @since 3.1.1 + */ + public function setColumnValue($columnName,$value) + { + $className=get_class($this); + if(isset(self::$_columnMapping[$className][$columnName])) + $columnName=self::$_columnMapping[$className][$columnName]; + $this->$columnName=$value; + } } ?> \ No newline at end of file diff --git a/framework/Data/ActiveRecord/TActiveRecordGateway.php b/framework/Data/ActiveRecord/TActiveRecordGateway.php index e081e19c..6c5bc07c 100644 --- a/framework/Data/ActiveRecord/TActiveRecordGateway.php +++ b/framework/Data/ActiveRecord/TActiveRecordGateway.php @@ -277,7 +277,7 @@ class TActiveRecordGateway extends TComponent foreach($tableInfo->getColumns() as $name => $column) { if($column->hasSequence()) - $record->{$name} = $command->getLastInsertID($column->getSequenceName()); + $record->setColumnValue($name,$command->getLastInsertID($column->getSequenceName())); } } @@ -293,7 +293,7 @@ class TActiveRecordGateway extends TComponent { if($column->getIsExcluded()) continue; - $value = $record->{$name}; + $value = $record->getColumnValue($name); if(!$column->getAllowNull() && $value===null && !$column->hasSequence()) { throw new TActiveRecordException( @@ -329,7 +329,7 @@ class TActiveRecordGateway extends TComponent { if($column->getIsExcluded()) continue; - $value = $record->{$name}; + $value = $record->getColumnValue($name); if(!$column->getAllowNull() && $value===null) { throw new TActiveRecordException( @@ -367,7 +367,7 @@ class TActiveRecordGateway extends TComponent foreach($tableInfo->getColumns() as $name=>$column) { if($column->getIsPrimaryKey()) - $primary[$name] = $record->{$name}; + $primary[$name] = $record->getColumnValue($name); } return $primary; } diff --git a/tests/FunctionalTests/active-controls/tests/ActiveDropDownListTestCase.php b/tests/FunctionalTests/active-controls/tests/ActiveDropDownListTestCase.php index 5899a6ba..cb5d349c 100644 --- a/tests/FunctionalTests/active-controls/tests/ActiveDropDownListTestCase.php +++ b/tests/FunctionalTests/active-controls/tests/ActiveDropDownListTestCase.php @@ -29,13 +29,13 @@ class ActiveDropDownListTestCase extends SeleniumTestCase $this->pause(800); $this->select("list2", "value 1 - item 4"); $this->pause(800); - $this->assertText("label1", "Selection 2: value 1 - item 4"); + $this->assertText("label2", "Selection 2: value 1 - item 4"); $this->select("list1", "item 3"); $this->pause(800); $this->select("list2", "value 3 - item 5"); $this->pause(800); - $this->assertText("label1", "Selection 2: value 3 - item 5"); + $this->assertText("label2", "Selection 2: value 3 - item 5"); $this->click('button4'); $this->pause(800); -- cgit v1.2.3