diff options
Diffstat (limited to 'framework/Data/ActiveRecord')
7 files changed, 235 insertions, 59 deletions
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php index d7278d64..d4ff07e5 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php @@ -77,8 +77,7 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation */
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($this->getSourceRecord(),$fkObject);
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_keys($fkeys);
$fields = array_values($fkeys);
@@ -87,6 +86,16 @@ class TActiveRecordBelongsTo extends TActiveRecordRelation $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
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($this->getSourceRecord(),$fkObject);
+ }
/**
* Sets the foreign objects to the given property on the source object.
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php index cbba3ebd..f7426862 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php @@ -1,4 +1,4 @@ -<?php +<?php
/**
* TActiveRecordHasMany class file.
*
@@ -75,8 +75,7 @@ class TActiveRecordHasMany extends TActiveRecordRelation */
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_values($fkeys);
$fields = array_keys($fkeys);
@@ -87,6 +86,16 @@ class TActiveRecordHasMany extends TActiveRecordRelation }
/**
+ * @return array foreign key field names as key and object properties as value.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ }
+
+ /**
* Updates the associated foreign objects.
* @return boolean true if all update are success (including if no update was required), false otherwise .
*/
@@ -113,5 +122,5 @@ class TActiveRecordHasMany extends TActiveRecordRelation 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 0e176607..564d3d22 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php @@ -37,7 +37,7 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelation'); *
* public static $RELATIONS = array
* (
- * 'Categories' => array(self::HAS_MANY, 'CategoryRecord', 'Article_Category')
+ * 'Categories' => array(self::MANY_TO_MANY, 'CategoryRecord', 'Article_Category')
* );
*
* public static function finder($className=__CLASS__)
@@ -54,7 +54,7 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelation'); *
* public static $RELATIONS = array
* (
- * 'Articles' => array(self::HAS_MANY, 'ArticleRecord', 'Article_Category')
+ * 'Articles' => array(self::MANY_TO_MANY, 'ArticleRecord', 'Article_Category')
* );
*
* public static function finder($className=__CLASS__)
@@ -96,17 +96,22 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation */
protected function collectForeignObjects(&$results)
{
- $association = $this->getAssociationTable();
- $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord());
-
+ list($sourceKeys, $foreignKeys) = $this->getRelationForeignKeys();
$properties = array_values($sourceKeys);
-
$indexValues = $this->getIndexValues($properties, $results);
+ $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys);
+ }
+ /**
+ * @return array 2 arrays of source keys and foreign keys from the association table.
+ */
+ public function getRelationForeignKeys()
+ {
+ $association = $this->getAssociationTable();
+ $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord(), true);
$fkObject = $this->getContext()->getForeignRecordFinder();
$foreignKeys = $this->findForeignKeys($association, $fkObject);
-
- $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys);
+ return array($sourceKeys, $foreignKeys);
}
/**
@@ -182,7 +187,7 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation */
protected function fetchForeignObjects(&$results,$foreignKeys,$indexValues,$sourceKeys)
{
- $criteria = $this->getContext()->getCriteria();
+ $criteria = $this->getCriteria();
$finder = $this->getContext()->getForeignRecordFinder();
$registry = $finder->getRecordManager()->getObjectStateRegistry();
$type = get_class($finder);
@@ -198,7 +203,6 @@ class TActiveRecordHasManyAssociation extends TActiveRecordRelation $collections[$hash][] = $obj;
$registry->registerClean($obj);
}
-
$this->setResultCollection($results, $collections, array_values($sourceKeys));
}
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php index b22428ae..e5a36659 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php @@ -92,9 +92,7 @@ class TActiveRecordHasOne extends TActiveRecordRelation */
protected function collectForeignObjects(&$results)
{
- $fkObject = $this->getContext()->getForeignRecordFinder();
- $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord());
-
+ $fkeys = $this->getRelationForeignKeys();
$properties = array_values($fkeys);
$fields = array_keys($fkeys);
@@ -102,6 +100,16 @@ class TActiveRecordHasOne extends TActiveRecordRelation $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
+ */
+ public function getRelationForeignKeys()
+ {
+ $fkObject = $this->getContext()->getForeignRecordFinder();
+ return $this->findForeignKeys($fkObject, $this->getSourceRecord());
+ }
/**
* Sets the foreign objects to the given property on the source object.
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php index 5a3ea50e..8e9cc9b5 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php @@ -26,10 +26,12 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext'); abstract class TActiveRecordRelation
{
private $_context;
+ private $_criteria;
- public function __construct(TActiveRecordRelationContext $context)
+ public function __construct(TActiveRecordRelationContext $context, $criteria)
{
$this->_context = $context;
+ $this->_criteria = $criteria;
}
/**
@@ -39,6 +41,14 @@ abstract class TActiveRecordRelation {
return $this->_context;
}
+
+ /**
+ * @return TActiveRecordCriteria
+ */
+ protected function getCriteria()
+ {
+ return $this->_criteria;
+ }
/**
* @return TActiveRecord
@@ -75,6 +85,16 @@ abstract class TActiveRecordRelation array_push($stack,$this); //call it later
return $results;
}
+
+ /**
+ * Fetch results for current relationship.
+ * @return boolean always true.
+ */
+ public function fetchResultsInto($obj)
+ {
+ $this->collectForeignObjects($obj);
+ return true;
+ }
/**
* Returns foreign keys in $fromRecord with source column names as key
@@ -84,7 +104,7 @@ abstract class TActiveRecordRelation * @param TActiveRecord $matchesRecord
* @return array foreign keys with source column names as key and foreign column names as value.
*/
- protected function findForeignKeys($from, $matchesRecord)
+ protected function findForeignKeys($from, $matchesRecord, $loose=false)
{
$gateway = $matchesRecord->getRecordGateway();
$matchingTableName = $gateway->getRecordTableInfo($matchesRecord)->getTableName();
@@ -93,13 +113,42 @@ abstract class TActiveRecordRelation $tableInfo = $gateway->getRecordTableInfo($from);
foreach($tableInfo->getForeignKeys() as $fkeys)
{
- if($fkeys['table']===$matchingTableName)
- return $fkeys['keys'];
+ if(strtolower($fkeys['table'])===strtolower($matchingTableName))
+ {
+ if(!$loose && $this->getContext()->hasFkField())
+ return $this->getFkFields($fkeys['keys']);
+ else
+ return $fkeys['keys'];
+ }
}
$matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName();
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
+ */
+ 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
+ * are ignored.
+ */
+ private function getFkFields($fkeys)
+ {
+ $matching = array();
+ preg_match_all('/\s*(\S+\.)?([\w-]+)\s*/', $this->getContext()->getFkField(), $matching);
+ $fields = array();
+ foreach($fkeys as $fkName => $field)
+ {
+ if(in_array($fkName, $matching[2]))
+ $fields[$fkName] = $field;
+ }
+ return $fields;
+ }
/**
* @param mixed object or array to be hashed
@@ -122,9 +171,8 @@ abstract class TActiveRecordRelation */
protected function findForeignObjects($fields, $indexValues)
{
- $criteria = $this->getContext()->getCriteria();
$finder = $this->getContext()->getForeignRecordFinder();
- return $finder->findAllByIndex($criteria, $fields, $indexValues);
+ return $finder->findAllByIndex($this->_criteria, $fields, $indexValues);
}
/**
diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php index 189d2c5e..d83aa63a 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php @@ -32,13 +32,12 @@ class TActiveRecordRelationContext private $_property;
private $_sourceRecord;
- private $_criteria;
- private $_relation;
+ private $_relation; //data from an entry of TActiveRecord::$RELATION
+ private $_fkeys;
- public function __construct($source, $property=null, $criteria=null)
+ public function __construct($source, $property=null)
{
$this->_sourceRecord=$source;
- $this->_criteria=$criteria;
if($property!==null)
list($this->_property, $this->_relation) = $this->getSourceRecordRelation($property);
}
@@ -48,7 +47,6 @@ class TActiveRecordRelationContext * property from the $RELATIONS static property in TActiveRecord.
* @param string relation property name
* @return array array($propertyName, $relation) relation definition.
- * @throws TActiveRecordException if property is not defined or missing.
*/
protected function getSourceRecordRelation($property)
{
@@ -58,8 +56,6 @@ class TActiveRecordRelationContext if(strtolower($name)===$property)
return array($name, $relation);
}
- throw new TActiveRecordException('ar_undefined_relation_prop',
- $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);
}
/**
@@ -71,6 +67,15 @@ class TActiveRecordRelationContext return $class->getStaticPropertyValue(self::RELATIONS_CONST);
}
+ /**
+ * @return boolean true if the relation is defined in TActiveRecord::$RELATIONS
+ * @since 3.1.2
+ */
+ public function hasRecordRelation()
+ {
+ return $this->_relation!==null;
+ }
+
public function getPropertyValue()
{
$obj = $this->getSourceRecord();
@@ -86,14 +91,6 @@ class TActiveRecordRelationContext }
/**
- * @return TActiveRecordCriteria sql query criteria for fetching the related record.
- */
- public function getCriteria()
- {
- return $this->_criteria;
- }
-
- /**
* @return TActiveRecord the active record instance that queried for its related records.
*/
public function getSourceRecord()
@@ -110,6 +107,18 @@ class TActiveRecordRelationContext }
/**
+ * @return array foreign key of this relations, the keys is dependent on the
+ * relationship type.
+ * @since 3.1.2
+ */
+ public function getRelationForeignKeys()
+ {
+ if($this->_fkeys===null)
+ $this->_fkeys=$this->getRelationHandler()->getRelationForeignKeys();
+ return $this->_fkeys;
+ }
+
+ /**
* @return string HAS_MANY, HAS_ONE, or BELONGS_TO
*/
public function getRelationType()
@@ -118,6 +127,25 @@ class TActiveRecordRelationContext }
/**
+ * @return string foreign key field names, comma delimited.
+ * @since 3.1.2
+ */
+ public function getFkField()
+ {
+ return $this->_relation[2];
+ }
+
+ /**
+ * @return boolean true if the 3rd element of an TActiveRecord::$RELATION entry is set.
+ * @since 3.1.2
+ */
+ public function hasFkField()
+ {
+ $notManyToMany = $this->getRelationType() !== TActiveRecord::MANY_TO_MANY;
+ return $notManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]);
+ }
+
+ /**
* @return string the M-N relationship association table name.
*/
public function getAssociationTable()
@@ -130,7 +158,8 @@ class TActiveRecordRelationContext */
public function hasAssociationTable()
{
- return isset($this->_relation[2]);
+ $isManyToMany = $this->getRelationType() === TActiveRecord::MANY_TO_MANY;
+ return $isManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]);
}
/**
@@ -145,29 +174,33 @@ class TActiveRecordRelationContext * Creates and return the TActiveRecordRelation handler for specific relationships.
* An instance of TActiveRecordHasOne, TActiveRecordBelongsTo, TActiveRecordHasMany,
* or TActiveRecordHasManyAssocation will be returned.
+ * @param TActiveRecordCriteria search criteria
* @return TActiveRecordRelation record relationship handler instnace.
+ * @throws TActiveRecordException if property is not defined or missing.
*/
- public function getRelationHandler()
+ public function getRelationHandler($criteria=null)
{
+ if(!$this->hasRecordRelation())
+ {
+ throw new TActiveRecordException('ar_undefined_relation_prop',
+ $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);
+ }
+ if($criteria===null)
+ $criteria = new TActiveRecordCriteria;
switch($this->getRelationType())
{
case TActiveRecord::HAS_MANY:
- if(!$this->hasAssociationTable())
- {
- Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany');
- return new TActiveRecordHasMany($this);
- }
- else
- {
- Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasManyAssociation');
- return new TActiveRecordHasManyAssociation($this);
- }
+ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany');
+ return new TActiveRecordHasMany($this, $criteria);
+ case TActiveRecord::MANY_TO_MANY:
+ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasManyAssociation');
+ return new TActiveRecordHasManyAssociation($this, $criteria);
case TActiveRecord::HAS_ONE:
Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasOne');
- return new TActiveRecordHasOne($this);
+ return new TActiveRecordHasOne($this, $criteria);
case TActiveRecord::BELONGS_TO:
Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordBelongsTo');
- return new TActiveRecordBelongsTo($this);
+ return new TActiveRecordBelongsTo($this, $criteria);
default:
throw new TActiveRecordException('ar_invalid_relationship');
}
diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php index b53cdf45..446c87e4 100644 --- a/framework/Data/ActiveRecord/TActiveRecord.php +++ b/framework/Data/ActiveRecord/TActiveRecord.php @@ -94,9 +94,10 @@ Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext'); */ abstract class TActiveRecord extends TComponent { - const HAS_MANY='HAS_MANY'; const BELONGS_TO='BELONGS_TO'; const HAS_ONE='HAS_ONE'; + const HAS_MANY='HAS_MANY'; + const MANY_TO_MANY='MANY_TO_MANY'; private static $_columnMapping=array(); @@ -110,6 +111,8 @@ abstract class TActiveRecord extends TComponent */ public static $COLUMN_MAPPING=array(); + private static $_relations=array(); + /** * This static variable defines the relationships. * The keys are public variable/property names defined in the AR class. @@ -562,15 +565,77 @@ abstract class TActiveRecord extends TComponent /** * Returns the active record relationship handler for $RELATION with key * value equal to the $property value. - * @param string relationship property name. + * @param string relationship/property name corresponding to keys in $RELATION array. * @param array method call arguments. * @return TActiveRecordRelation */ - protected function getRelationHandler($property,$args) + protected function getRelationHandler($property,$args=array()) { $criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1)); - $context = new TActiveRecordRelationContext($this, $property, $criteria); - return $context->getRelationHandler(); + return $this->getRelationContext($property)->getRelationHandler($criteria); + } + + /** + * Gets a static copy of the relationship context for given property (a key + * in $RELATION), 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 + * the active record relationships for given property, null if invalid relationship + * @since 3.1.2 + */ + 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]; + } + + /** + * Tries to load the relationship results for the given property. The $property + * value should correspond to an entry key in the $RELATION array. + * This method can be used to lazy load relationships. + * <code> + * class TeamRecord extends TActiveRecord + * { + * ... + * + * private $_players; + * public static $RELATION=array + * ( + * 'players' => array(self::HAS_MANY, 'PlayerRecord'), + * ); + * + * public function setPlayers($array) + * { + * $this->_players=$array; + * } + * + * public function getPlayers() + * { + * if($this->_players===null) + * $this->fetchResultsFor('players'); + * return $this->_players; + * } + * } + * Usage example: + * $team = TeamRecord::finder()->findByPk(1); + * var_dump($team->players); //uses lazy load to fetch 'players' relation + * </code> + * @param string relationship/property name corresponding to keys in $RELATION array. + * @return boolean true if relationship exists, false otherwise. + * @since 3.1.2 + */ + protected function fetchResultsFor($property) + { + if( ($context=$this->getRelationContext($property)) !== null) + return $context->getRelationHandler()->fetchResultsInto($this); + return false; } /** |