summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY1
-rw-r--r--demos/quickstart/protected/pages/Database/ActiveRecord.page57
-rw-r--r--demos/quickstart/protected/pages/GettingStarted/NewFeatures.page1
-rw-r--r--framework/Data/ActiveRecord/TActiveRecord.php90
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordGateway.php8
-rw-r--r--tests/FunctionalTests/active-controls/tests/ActiveDropDownListTestCase.php4
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 @@
</ul>
<h2>Design Implications</h2>
<p>
-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 <tt>Customer</tt> 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 <tt>Customer</tt> object, the next time you ask for
that customer you get back another instance of a <tt>Customer</tt> object. This implies that a strict
comparison (i.e., using <tt>===</tt>) will return false, while loose comparison (i.e., using <tt>==</tt>) will
-return true if the object values are equal by loose comparison.
+return true if the object values are equal by loose comparison.
<p>
<p>
This is design implication related to the following question.
-<i>"Do you think of the customer as an object, of which there's only one,
+<i>"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 <b>copies</b> of the database?"</i>
-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.
-<i>"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.
+<i>"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."</i> [A. Hejlsberg 2003]
</p>
@@ -1059,6 +1059,45 @@ arrays. E.g. <tt>$player->skills[] = new SkillRecord()</tt>. If <tt>array</tt> w
will be thrown.
</p>
+<h2>Column Mapping</h2>
+<p>
+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.
+</p>
+<p>
+To use column mapping, declare a static array named <tt>COLUMN_MAPPING</tt> in the Active Record class.
+The keys of the array are column names (called <i>physical column names</i>) as defined in the database
+schema, while the values are corresponding property names (called <i>logical column names</i>) 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 <tt>COLUMN_MAPPING</tt>.
+</p>
+<com:TTextHighlighter Language="php" CssClass="source block-content">
+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;
+ //....
+}
+</com:TTextHighlighter>
+<p>
+With the above column mapping, we can address <tt>first_name</tt> using <tt>$userRecord->firstName</tt>
+instead of <tt>$userRecord->first_name</tt>. This helps separation of logic and model.
+</p>
+
<h2 id="138054">References</h2>
<ul id="u3" class="block-content">
<li>Fowler et. al. <i>Patterns of Enterprise Application Architecture</i>,
diff --git a/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page b/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page
index 9db74476..595bda00 100644
--- a/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page
+++ b/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page
@@ -19,6 +19,7 @@ This page summarizes the main new features that are introduced in each PRADO rel
<li>Added a new page state persister <tt>TCachePageStatePersister</tt>. It allows page state to be stored using a cache module (e.g. TMemCache, TDbCache, etc.)
<li>Added support to the <a href="?page=Advanced.Auth">auth framework</a> to allow remembering login.</li>
<li>Added support to display a prompt item in TDropDownList and TListBox (something like 'Please select:' as the first list item.)</li>
+<li>Added support to <a href="?page=Database.ActiveRecord">column mapping in Active Record</a>.</li>
</ul>
<h2 id="8006">Version 3.1.0</h2>
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.
* </code>
*
+ * 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:
+ * <code>
+ * class UserRecord extends TActiveRecord
+ * {
+ * const TABLE='users';
+ * protected static $COLUMN_MAPPING=array
+ * (
+ * 'user_id'=>'username',
+ * 'email_address'=>'email',
+ * );
+ * public $username;
+ * pulbic $email;
+ * }
+ * </code>
+ * 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 <weizho[at]gmail[dot]com>
* @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);