summaryrefslogtreecommitdiff
path: root/framework/Data/ActiveRecord
diff options
context:
space:
mode:
Diffstat (limited to 'framework/Data/ActiveRecord')
-rw-r--r--framework/Data/ActiveRecord/TActiveRecord.php156
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordGateway.php5
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordRelation.php124
3 files changed, 227 insertions, 58 deletions
diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php
index 1ea06a60..509d23f6 100644
--- a/framework/Data/ActiveRecord/TActiveRecord.php
+++ b/framework/Data/ActiveRecord/TActiveRecord.php
@@ -12,14 +12,15 @@
Prado::using('System.Data.ActiveRecord.TActiveRecordManager');
Prado::using('System.Data.ActiveRecord.TActiveRecordCriteria');
+Prado::using('System.Data.ActiveRecord.TActiveRecordRelation');
/**
* Base class for active records.
*
* An active record creates an object that wraps a row in a database table
- * or view, encapsulates the database access, and adds domain logic on that data.
- *
- * Active record objects are stateful, this is main difference between the
+ * or view, encapsulates the database access, and adds domain logic on that data.
+ *
+ * Active record objects are stateful, this is main difference between the
* TActiveRecord implementation and the TTableGateway implementation.
*
* The essence of an Active Record is an object model of the
@@ -68,6 +69,10 @@ Prado::using('System.Data.ActiveRecord.TActiveRecordCriteria');
*/
abstract class TActiveRecord extends TComponent
{
+ const HAS_MANY='HAS_MANY';
+ const BELONGS_TO='BELONGS_TO';
+ const HAS_ONE='HAS_ONE';
+
/**
* @var boolean true if this class is read only.
*/
@@ -143,10 +148,10 @@ abstract class TActiveRecord extends TComponent
}
/**
- * Returns the instance of a active record finder for a particular class.
- * The finder objects are static instances for each ActiveRecord class.
- * This means that event handlers bound to these finder instances are class wide.
- * Create a new instance of the ActiveRecord class if you wish to bound the
+ * Returns the instance of a active record finder for a particular class.
+ * The finder objects are static instances for each ActiveRecord class.
+ * This means that event handlers bound to these finder instances are class wide.
+ * Create a new instance of the ActiveRecord class if you wish to bound the
* event handlers to object instance.
* @param string active record class name.
* @return TActiveRecord active record finder instance.
@@ -175,14 +180,14 @@ abstract class TActiveRecord extends TComponent
public function getRecordManager()
{
return TActiveRecordManager::getInstance();
- }
-
- /**
- * @return TActiveRecordGateway record table gateway.
- */
- public function getRecordGateway()
- {
- return $this->getRecordManager()->getRecordGateway();
+ }
+
+ /**
+ * @return TActiveRecordGateway record table gateway.
+ */
+ public function getRecordGateway()
+ {
+ return $this->getRecordManager()->getRecordGateway();
}
/**
@@ -281,16 +286,16 @@ abstract class TActiveRecord extends TComponent
//try the cache (the cache object must be clean)
if(!is_null($obj = $registry->getCachedInstance($data)))
- return $obj;
-
- $gateway = $this->getRecordManager()->getRecordGateway();
+ return $obj;
+
+ $gateway = $this->getRecordManager()->getRecordGateway();
//create and populate the object
$obj = Prado::createComponent($type);
- $tableInfo = $gateway->getRecordTableInfo($obj);
- foreach($tableInfo->getColumns()->getKeys() as $name)
- {
- if(isset($data[$name]))
+ $tableInfo = $gateway->getRecordTableInfo($obj);
+ foreach($tableInfo->getColumns()->getKeys() as $name)
+ {
+ if(isset($data[$name]))
$obj->{$name} = $data[$name];
}
@@ -420,6 +425,17 @@ abstract class TActiveRecord extends TComponent
}
/**
+ *
+ *
+ */
+ public function findAllByIndex($criteria,$fields,$values)
+ {
+ $gateway = $this->getRecordManager()->getRecordGateway();
+ $result = $gateway->findRecordsByIndex($this,$criteria,$fields,$values);
+ return $this->collectObjects($result);
+ }
+
+ /**
* Find the number of records.
* @param string|TActiveRecordCriteria SQL condition or criteria object.
* @param mixed parameter values.
@@ -435,6 +451,24 @@ abstract class TActiveRecord extends TComponent
}
/**
+ *
+ * @return TActiveRecordRelation
+ */
+ protected function getRecordRelation($property,$args)
+ {
+ $criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1));
+ $relation = $this->{$property};
+ switch($relation[0])
+ {
+ case self::HAS_MANY:
+ $finder = self::finder($relation[1]);
+ return new TActiveRecordHasMany($this, $criteria, $finder, $property);
+ default:
+ throw new TException('Not done yet');
+ }
+ }
+
+ /**
* Dynamic find method using parts of method name as search criteria.
* Method name starting with "findBy" only returns 1 record.
* Method name starting with "findAllBy" returns 0 or more records.
@@ -466,7 +500,12 @@ abstract class TActiveRecord extends TComponent
public function __call($method,$args)
{
$delete =false;
- if($findOne = substr(strtolower($method),0,6)==='findby')
+ if(substr(strtolower($method),0,4)==='with')
+ {
+ $property= $method[4]==='_' ? substr($method,5) : substr($method,4);
+ return $this->getRecordRelation($property, $args);
+ }
+ else if($findOne = substr(strtolower($method),0,6)==='findby')
$condition = $method[6]==='_' ? substr($method,7) : substr($method,6);
else if(substr(strtolower($method),0,9)==='findallby')
$condition = $method[9]==='_' ? substr($method,10) : substr($method,9);
@@ -492,7 +531,7 @@ abstract class TActiveRecord extends TComponent
* @param array additional parameters obtained from function_get_args().
* @return TSqlCriteria criteria object.
*/
- protected function getCriteria($criteria, $parameters, $args)
+ protected function getCriteria($criteria, $parameters, $args=array())
{
if(is_string($criteria))
{
@@ -502,40 +541,41 @@ abstract class TActiveRecord extends TComponent
else if($criteria instanceof TSqlCriteria)
return $criteria;
else
- throw new TActiveRecordException('ar_invalid_criteria');
+ return new TActiveRecordCriteria();
+ //throw new TActiveRecordException('ar_invalid_criteria');
+ }
+
+ /**
+ * Raised when a command is prepared and parameter binding is completed.
+ * The parameter object is TDataGatewayEventParameter of which the
+ * {@link TDataGatewayEventParameter::getCommand Command} property can be
+ * inspected to obtain the sql query to be executed.
+ *
+ * Note well that the finder objects obtained from ActiveRecord::finder()
+ * method are static objects. This means that the event handlers are
+ * bound to a static finder object and not to each distinct active record object.
+ * @param TDataGatewayEventParameter
+ */
+ public function onCreateCommand($param)
+ {
+ $this->raiseEvent('OnCreateCommand', $this, $param);
+ }
+
+ /**
+ * Raised when a command is executed and the result from the database was returned.
+ * The parameter object is TDataGatewayResultEventParameter of which the
+ * {@link TDataGatewayEventParameter::getResult Result} property contains
+ * the data return from the database. The data returned can be changed
+ * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
+ *
+ * Note well that the finder objects obtained from ActiveRecord::finder()
+ * method are static objects. This means that the event handlers are
+ * bound to a static finder object and not to each distinct active record object.
+ * @param TDataGatewayResultEventParameter
+ */
+ public function onExecuteCommand($param)
+ {
+ $this->raiseEvent('OnExecuteCommand', $this, $param);
}
-
- /**
- * Raised when a command is prepared and parameter binding is completed.
- * The parameter object is TDataGatewayEventParameter of which the
- * {@link TDataGatewayEventParameter::getCommand Command} property can be
- * inspected to obtain the sql query to be executed.
- *
- * Note well that the finder objects obtained from ActiveRecord::finder()
- * method are static objects. This means that the event handlers are
- * bound to a static finder object and not to each distinct active record object.
- * @param TDataGatewayEventParameter
- */
- public function onCreateCommand($param)
- {
- $this->raiseEvent('OnCreateCommand', $this, $param);
- }
-
- /**
- * Raised when a command is executed and the result from the database was returned.
- * The parameter object is TDataGatewayResultEventParameter of which the
- * {@link TDataGatewayEventParameter::getResult Result} property contains
- * the data return from the database. The data returned can be changed
- * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
- *
- * Note well that the finder objects obtained from ActiveRecord::finder()
- * method are static objects. This means that the event handlers are
- * bound to a static finder object and not to each distinct active record object.
- * @param TDataGatewayResultEventParameter
- */
- public function onExecuteCommand($param)
- {
- $this->raiseEvent('OnExecuteCommand', $this, $param);
- }
}
?>
diff --git a/framework/Data/ActiveRecord/TActiveRecordGateway.php b/framework/Data/ActiveRecord/TActiveRecordGateway.php
index c3239c5c..40948999 100644
--- a/framework/Data/ActiveRecord/TActiveRecordGateway.php
+++ b/framework/Data/ActiveRecord/TActiveRecordGateway.php
@@ -221,6 +221,11 @@ class TActiveRecordGateway extends TComponent
return $this->getCommand($record)->findBySql($criteria);
}
+ public function findRecordsByIndex(TActiveRecord $record, $criteria, $fields, $values)
+ {
+ return $this->getCommand($record)->findAllByIndex($criteria,$fields,$values);
+ }
+
/**
* Returns the number of records that match the given criteria.
* @param TActiveRecord active record finder instance.
diff --git a/framework/Data/ActiveRecord/TActiveRecordRelation.php b/framework/Data/ActiveRecord/TActiveRecordRelation.php
new file mode 100644
index 00000000..6fae5499
--- /dev/null
+++ b/framework/Data/ActiveRecord/TActiveRecordRelation.php
@@ -0,0 +1,124 @@
+<?php
+
+abstract class TActiveRecordRelation
+{
+}
+
+class TActiveRecordHasMany extends TActiveRecordRelation
+{
+ private $source;
+ private $dependent;
+ private $property;
+ private $criteria;
+
+ private $fkeys;
+
+ public function __construct($source,$criteria,$dependent,$property)
+ {
+ $this->source=$source;
+ $this->criteria=$criteria;
+ $this->dependent=$dependent;
+ $this->property=$property;
+ }
+
+ public function __call($method,$args)
+ {
+ $results = call_user_func_array(array($this->source,$method),$args);
+ $fkResults = $this->getForeignIndexResults($results);
+ $this->matchResultCollection($results,$fkResults);
+ return $results;
+ }
+ protected function getForeignIndexResults($results)
+ {
+ if(!is_array($results))
+ $results = array($results);
+ $fkeys = $this->getForeignKeys();
+ $values = $this->getForeignKeyIndices($results, $fkeys);
+ $fields = array_keys($fkeys);
+ return $this->dependent->findAllByIndex($this->criteria, $fields, $values);
+ }
+
+ protected function matchResultCollection(&$results,&$fkResults)
+ {
+ $keys = $this->getForeignKeys();
+ $collections=array();
+ foreach($fkResults as $fkObject)
+ {
+ $objId=array();
+ foreach($keys as $fkName=>$name)
+ $objId[] = $fkObject->{$fkName};
+ $collections[$this->getObjectId($objId)][]=$fkObject;
+ }
+ if(is_array($results))
+ {
+ for($i=0,$k=count($results);$i<$k;$i++)
+ {
+ $this->setFkObjectProperty($results[$i], $collections);
+ }
+ }
+ else
+ {
+ $this->setFkObjectProperty($results, $collections);
+ }
+ }
+
+ function setFKObjectProperty($source, &$collections)
+ {
+ $objId=array();
+ foreach($this->getForeignKeys() as $fkName=>$name)
+ $objId[] = $source->{$name};
+ $key = $this->getObjectId($objId);
+ $source->{$this->property} = isset($collections[$key]) ? $collections[$key] : array();
+ }
+
+ protected function getObjectId($objId)
+ {
+ return sprintf('%x',crc32(serialize($objId)));
+ }
+
+ protected function getForeignKeys()
+ {
+ if($this->fkeys===null)
+ {
+ $gateway = $this->dependent->getRecordGateway();
+ $depTableInfo = $gateway->getRecordTableInfo($this->dependent);
+ $fks = $depTableInfo->getForeignKeys();
+ $sourceTable = $gateway->getRecordTableInfo($this->source)->getTableName();
+ foreach($fks as $relation)
+ {
+ if($relation['table']===$sourceTable)
+ {
+ $this->fkeys=$relation['keys'];
+ break;
+ }
+ }
+ if(!$this->fkeys)
+ throw new TActiveRecordException('no fk defined for '.$depTableInfo->getTableFullName());
+ }
+ return $this->fkeys;
+ }
+
+ protected function getForeignKeyIndices($results,$keys)
+ {
+ $values = array();
+ foreach($results as $result)
+ {
+ $value = array();
+ foreach($keys as $name)
+ $value[] = $result->{$name};
+ $values[] = $value;
+ }
+ return $values;
+ }
+}
+
+class TActiveRecordHasOne extends TActiveRecordRelation
+{
+}
+
+class TActiveRecordBelongsTo extends TActiveRecordRelation
+{
+
+}
+
+?> \ No newline at end of file