From dbb73305b29a8cc3b160688e8977049af785ab32 Mon Sep 17 00:00:00 2001 From: xue <> Date: Sat, 13 Oct 2007 01:48:28 +0000 Subject: Active Record now supports implicitly declared related properties --- HISTORY | 1 + .../protected/pages/Database/ActiveRecord.page | 61 ++++++--- .../Relations/TActiveRecordBelongsTo.php | 7 +- .../Relations/TActiveRecordRelation.php | 14 +- .../Relations/TActiveRecordRelationContext.php | 48 ++----- framework/Data/ActiveRecord/TActiveRecord.php | 152 ++++++++++++++++++--- 6 files changed, 200 insertions(+), 83 deletions(-) diff --git a/HISTORY b/HISTORY index f3e6881c..8f6507cc 100644 --- a/HISTORY +++ b/HISTORY @@ -5,6 +5,7 @@ CHG: Changed TConditional so that the controls in its template behave like they CHG: Active Record many-to-many relationship change from self::HAS_MANY to self::MANY_TO_MANY (Wei) ENH: Active Record supports multiple foreign references of the same table (Wei) ENH: Added TDbCommand.queryColumn() (Qiang) +ENH: Active Record now supports implicitly declared related properties (Qiang) NEW: Added TDbLogRoute (Qiang) NEW: Added TDataRenderer and TItemDataRenderer (Qiang) NEW: Ticket#544 - Added TXCache (Wei) diff --git a/demos/quickstart/protected/pages/Database/ActiveRecord.page b/demos/quickstart/protected/pages/Database/ActiveRecord.page index cb10d184..d2793fa1 100644 --- a/demos/quickstart/protected/pages/Database/ActiveRecord.page +++ b/demos/quickstart/protected/pages/Database/ActiveRecord.page @@ -624,7 +624,7 @@ class TeamRecord extends TActiveRecord public $name; public $location; - public $players=array(); + public $players=array(); // this declaration is no longer needed since v3.1.2 //define the $player member having has many relationship with PlayerRecord public static $RELATIONS=array @@ -650,6 +650,16 @@ The second element is a string 'PlayerRecord' that corresponds to the class name of the PlayerRecord class.
+The foreign key constraint of the Players table is used to determine the corresponding Teams table's corresponding key names. This is done automatically handled @@ -657,9 +667,9 @@ in Active Record by inspecting the Players and Teams table def
The "has many" relationship in the above section defines a collection of foreign objects. In particular, we have that a TeamRecord has many (zero or more) @@ -726,9 +746,9 @@ class PlayerRecord extends TActiveRecord public $age; public $team_name; - public $team; - public $skills=array(); - public $profile; + public $team; // this declaration is no longer needed since v3.1.2 + public $skills=array(); // this declaration is no longer needed since v3.1.2 + public $profile; // this declaration is no longer needed since v3.1.2 public static $RELATIONS=array ( @@ -785,7 +805,7 @@ class ProfileRecord extends TActiveRecord public $player_id; public $salary; - public $player; + public $player; // this declaration is no longer needed since v3.1.2 public static $RELATIONS=array ( @@ -830,8 +850,8 @@ class Category extends TActiveRecord public $category_name; public $parent_cat_id; - public $parent_category; - public $child_categories=array(); + public $parent_category; // this declaration is no longer needed since v3.1.2 + public $child_categories=array(); // this declaration is no longer needed since v3.1.2 public static $RELATIONS=array ( @@ -870,7 +890,7 @@ in the Player_Skills association table using an inner join.
The Prado Active Record design implements the two stage approach. For the -Players-Skills M-N (many-to-many) entity relationship, we +Players-Skills M-N (many-to-many) entity relationship, we define a many-to-many relationship in the PlayerRecord class and in addition we may define a many-to-many relationship in the SkillRecord class as well. The following sample code defines the complete SkillRecord class with a @@ -885,7 +905,7 @@ class SkillRecord extends TActiveRecord public $skill_id; public $name; - public $players=array(); + public $players=array(); // this declaration is no longer needed since v3.1.2 public static $RELATIONS=array ( @@ -910,10 +930,10 @@ of the association table name.
@@ -975,7 +995,7 @@ class Item extends TActiveRecord //additional foreign item id defined in the association table public $related_item_id; - public $related_items=array(); + public $related_items=array(); // this declaration is no longer needed since v3.1.2 public static $RELATIONS=array ( @@ -1030,6 +1050,13 @@ PlayerSkillAssocation::finder()->deleteByPk(array('fk1','fk2'));
Using the with_xxx() methods will load the relationship record on demand. Retrieving the
related record using lazy loading (that is, only when those related objects are accessed) can be
achieved by using a feature of the TComponent that provides accessor methods. In particular,
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
index d4ff07e5..805739c1 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
@@ -81,12 +81,11 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation
$properties = array_keys($fkeys);
$fields = array_values($fkeys);
-
$indexValues = $this->getIndexValues($properties, $results);
$fkObjects = $this->findForeignObjects($fields, $indexValues);
$this->populateResult($results,$properties,$fkObjects,$fields);
}
-
+
/**
* @return array foreign key field names as key and object properties as value.
* @since 3.1.2
@@ -94,7 +93,7 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation
public function getRelationForeignKeys()
{
$fkObject = $this->getContext()->getForeignRecordFinder();
- return $this->findForeignKeys($this->getSourceRecord(),$fkObject);
+ return $this->findForeignKeys($this->getSourceRecord(),$fkObject);
}
/**
@@ -110,7 +109,7 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation
{
if(count($collections[$hash]) > 1)
throw new TActiveRecordException('ar_belongs_to_multiple_result');
- $source->setColumnValue($prop, $collections[$hash][0]);
+ $source->$prop=$collections[$hash][0];
}
}
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
index 8e9cc9b5..5bde4898 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
@@ -41,7 +41,7 @@ abstract class TActiveRecordRelation
{
return $this->_context;
}
-
+
/**
* @return TActiveRecordCriteria
*/
@@ -58,6 +58,8 @@ abstract class TActiveRecordRelation
return $this->getContext()->getSourceRecord();
}
+ abstract protected function collectForeignObjects(&$results);
+
/**
* Dispatch the method calls to the source record finder object. When
* an instance of TActiveRecord or an array of TActiveRecord is returned
@@ -85,7 +87,7 @@ abstract class TActiveRecordRelation
array_push($stack,$this); //call it later
return $results;
}
-
+
/**
* Fetch results for current relationship.
* @return boolean always true.
@@ -125,13 +127,13 @@ abstract class TActiveRecordRelation
throw new TActiveRecordException('ar_relations_missing_fk',
$tableInfo->getTableFullName(), $matching);
}
-
+
/**
* @return array foreign key field names as key and object properties as value.
- * @since 3.1.2
+ * @since 3.1.2
*/
abstract public function getRelationForeignKeys();
-
+
/**
* Find matching foreign key fields from the 3rd element of an entry in TActiveRecord::$RELATION.
* Assume field names consist of [\w-] character sets. Prefix to the field names ending with a dot
@@ -238,7 +240,7 @@ abstract class TActiveRecordRelation
{
$hash = $this->getObjectHash($source, $properties);
$prop = $this->getContext()->getProperty();
- $source->setColumnValue($prop, isset($collections[$hash]) ? $collections[$hash] : array());
+ $source->$prop=isset($collections[$hash]) ? $collections[$hash] : array();
}
}
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
index d83aa63a..8bc4362f 100644
--- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
+++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
@@ -25,46 +25,16 @@
*/
class TActiveRecordRelationContext
{
- /**
- * static property name in TActiveRecord that defines the record relationships.
- */
- const RELATIONS_CONST = 'RELATIONS';
-
private $_property;
- private $_sourceRecord;
+ private $_record;
private $_relation; //data from an entry of TActiveRecord::$RELATION
private $_fkeys;
- public function __construct($source, $property=null)
- {
- $this->_sourceRecord=$source;
- 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 array($propertyName, $relation) relation definition.
- */
- protected function getSourceRecordRelation($property)
- {
- $property = strtolower($property);
- foreach($this->getRecordRelationships() as $name => $relation)
- {
- if(strtolower($name)===$property)
- return array($name, $relation);
- }
- }
-
- /**
- * @return array the key and values of TActiveRecord::$RELATIONS
- */
- public function getRecordRelationships()
+ public function __construct($record, $property=null, $relation=null)
{
- $class = new ReflectionClass($this->_sourceRecord);
- return $class->getStaticPropertyValue(self::RELATIONS_CONST);
+ $this->_record=$record;
+ $this->_property=$property;
+ $this->_relation=$relation;
}
/**
@@ -95,7 +65,7 @@ class TActiveRecordRelationContext
*/
public function getSourceRecord()
{
- return $this->_sourceRecord;
+ return $this->_record;
}
/**
@@ -183,7 +153,7 @@ class TActiveRecordRelationContext
if(!$this->hasRecordRelation())
{
throw new TActiveRecordException('ar_undefined_relation_prop',
- $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);
+ $this->_property, get_class($this->_record), 'RELATIONS');
}
if($criteria===null)
$criteria = new TActiveRecordCriteria;
@@ -212,7 +182,7 @@ class TActiveRecordRelationContext
public function updateAssociatedRecords($updateBelongsTo=false)
{
$success=true;
- foreach($this->getRecordRelationships() as $property=>$relation)
+ foreach($this->_record->getRelations() as $property=>$relation)
{
$belongsTo = $relation[0]==TActiveRecord::BELONGS_TO;
if(($updateBelongsTo && $belongsTo) || (!$updateBelongsTo && !$belongsTo))
@@ -220,7 +190,7 @@ class TActiveRecordRelationContext
$obj = $this->getSourceRecord();
if(!$this->isEmptyFkObject($obj->getColumnValue($property)))
{
- $context = new self($this->getSourceRecord(),$property);
+ $context = new TActiveRecordRelationContext($this->getSourceRecord(),$property,$relation);
$success = $context->getRelationHandler()->updateAssociatedRecords() && $success;
}
}
diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php
index f28b81e4..a3265dc8 100644
--- a/framework/Data/ActiveRecord/TActiveRecord.php
+++ b/framework/Data/ActiveRecord/TActiveRecord.php
@@ -87,6 +87,27 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext');
* By using column mapping, we can regularize the naming convention of column names
* in active record.
*
+ * Since v3.1.2, TActiveRecord enhanced its support to access of foreign objects.
+ * By declaring a public static variable RELATIONS like the following, one can access
+ * the corresponding foreign objects easily:
+ *
+ * class UserRecord extends TActiveRecord
+ * {
+ * const TABLE='users';
+ * public static $RELATIONS=array
+ * (
+ * 'department'=>array(self::BELONGS_TO, 'DepartmentRecord', 'department_id'),
+ * 'contacts'=>array(self::HAS_MANY, 'ContactRecord', 'user_id'),
+ * );
+ * }
+ *
+ * In the above, the users table is related with departments table (represented by
+ * DepartmentRecord) and contacts table (represented by ContactRecord). Now, given a UserRecord
+ * instance $user, one can access its department and contacts simply by: $user->department and
+ * $user->contacts. No explicit data fetching is needed. Internally, the foreign objects are
+ * fetched in a lazy way, which avoids unnecessary overhead if the foreign objects are not accessed
+ * at all.
+ *
* @author Wei Zhuo