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 --- .../Relations/TActiveRecordBelongsTo.php | 7 +- .../Relations/TActiveRecordRelation.php | 14 +- .../Relations/TActiveRecordRelationContext.php | 48 ++----- framework/Data/ActiveRecord/TActiveRecord.php | 152 ++++++++++++++++++--- 4 files changed, 155 insertions(+), 66 deletions(-) (limited to 'framework/Data') 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 * @version $Id$ * @package System.Data.ActiveRecord @@ -99,8 +120,6 @@ abstract class TActiveRecord extends TComponent const HAS_MANY='HAS_MANY'; const MANY_TO_MANY='MANY_TO_MANY'; - private static $_columnMapping=array(); - /** * This static variable defines the column mapping. * The keys are physical column names as defined in database, @@ -110,8 +129,7 @@ abstract class TActiveRecord extends TComponent * @since 3.1.1 */ public static $COLUMN_MAPPING=array(); - - private static $_relations=array(); + private static $_columnMapping=array(); /** * This static variable defines the relationships. @@ -121,6 +139,7 @@ abstract class TActiveRecord extends TComponent * @since 3.1.1 */ public static $RELATIONS=array(); + private static $_relations=array(); /** * @var boolean true if this class is read only. @@ -132,6 +151,12 @@ abstract class TActiveRecord extends TComponent */ private $_connection; + /** + * @var array list of foreign objects. + * @since 3.1.2 + */ + private $_foreignObjects=array(); + /** * Prevent __call() method creating __sleep() when serializing. */ @@ -146,6 +171,7 @@ abstract class TActiveRecord extends TComponent public function __wake() { $this->setupColumnMapping(); + $this->setupRelations(); } /** @@ -161,6 +187,49 @@ abstract class TActiveRecord extends TComponent if($connection!==null) $this->_connection=$connection; $this->setupColumnMapping(); + $this->setupRelations(); + } + + /** + * Magic method for reading properties. + * This method is overriden to provide read access to the foreign objects via + * the key names declared in the RELATIONS array. + * @param string property name + * @return mixed property value. + * @since 3.1.2 + */ + public function __get($name) + { + if($this->hasRelation($name) && !$this->canGetProperty($name)) + { + $name2=strtolower($name); + if(!isset($this->_foreignObjects[$name2])) + $this->fetchResultsFor($name2); + if(isset($this->_foreignObjects[$name2])) + return $this->_foreignObjects[$name2]===false?null:$this->_foreignObjects[$name2]; + else + { + $this->_foreignObjects[$name2]=false; + return null; + } + } + return parent::__get($name); + } + + /** + * Magic method for writing properties. + * This method is overriden to provide write access to the foreign objects via + * the key names declared in the RELATIONS array. + * @param string property name + * @param mixed property value. + * @since 3.1.2 + */ + public function __set($name,$value) + { + if($this->hasRelation($name) && !$this->canSetProperty($name)) + $this->_foreignObjects[strtolower($name)]=$value===null?false:$value; + else + parent::__set($name,$value); } /** @@ -176,6 +245,22 @@ abstract class TActiveRecord extends TComponent } } + /** + * @since 3.1.2 + */ + private function setupRelations() + { + $className=get_class($this); + if(!isset(self::$_relations[$className])) + { + $class=new ReflectionClass($className); + $relations=array(); + foreach($class->getStaticPropertyValue('RELATIONS') as $key=>$value) + $relations[strtolower($key)]=$value; + self::$_relations[$className]=$relations; + } + } + /** * Copies data from an array or another object. * @throws TActiveRecordException if data is not array or not object. @@ -569,17 +654,22 @@ abstract class TActiveRecord extends TComponent * value equal to the $property value. * @param string relationship/property name corresponding to keys in $RELATION array. * @param array method call arguments. - * @return TActiveRecordRelation + * @return TActiveRecordRelation, null if the context or the handler doesn't exist */ protected function getRelationHandler($property,$args=array()) { - $criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1)); - return $this->getRelationContext($property)->getRelationHandler($criteria); + if(($context=$this->getRelationContext($property)) !== null) + { + $criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1)); + return $context->getRelationHandler($criteria); + } + else + return null; } /** * Gets a static copy of the relationship context for given property (a key - * in $RELATION), returns null if invalid relationship. Keeps a null + * in $RELATIONS), returns null if invalid relationship. Keeps a null * reference to all invalid relations called. * @param string relationship/property name corresponding to keys in $RELATION array. * @return TActiveRecordRelationContext object containing information on @@ -588,14 +678,10 @@ abstract class TActiveRecord extends TComponent */ protected function getRelationContext($property) { - $prop = get_class($this).':'.$property; - if(!isset(self::$_relations[$prop])) - { - $context = new TActiveRecordRelationContext($this, $property); - //keep a null reference to all non-existing relations called? - self::$_relations[$prop] = $context->hasRecordRelation() ? $context : null; - } - return self::$_relations[$prop]; + if(($relation=$this->getRelation($property))!==null) + return new TActiveRecordRelationContext($this,$property,$relation); + else + return null; } /** @@ -637,7 +723,8 @@ abstract class TActiveRecord extends TComponent { if( ($context=$this->getRelationContext($property)) !== null) return $context->getRelationHandler()->fetchResultsInto($this); - return false; + else + return false; } /** @@ -780,5 +867,36 @@ abstract class TActiveRecord extends TComponent $columnName=self::$_columnMapping[$className][$columnName]; $this->$columnName=$value; } + + /** + * @param string relation property name + * @return array relation definition for the specified property + * @since 3.1.2 + */ + public function getRelation($property) + { + $className=get_class($this); + $property=strtolower($property); + return isset(self::$_relations[$className][$property])?self::$_relations[$className][$property]:null; + } + + /** + * @return array all relation definitions declared in the AR class + * @since 3.1.2 + */ + public function getRelations() + { + return self::$_relations[get_class($this)]; + } + + /** + * @param string AR property name + * @return boolean whether a relation is declared for the specified AR property + * @since 3.1.2 + */ + public function hasRelation($property) + { + return isset(self::$_relations[get_class($this)][strtolower($property)]); + } } ?> \ No newline at end of file -- cgit v1.2.3