From be7deeed610871839ba166a5d4c8237e5cdbe864 Mon Sep 17 00:00:00 2001 From: xue <> Date: Tue, 4 Dec 2007 18:59:48 +0000 Subject: Active Record now supports query criteria for implicitly declared related properties --- HISTORY | 1 + .../protected/pages/Database/ActiveRecord.page | 60 ++++++++++++++-------- .../Relations/TActiveRecordRelationContext.php | 36 +++++++++---- framework/Data/Common/TDbCommandBuilder.php | 21 ++++---- framework/Data/DataGateway/TSqlCriteria.php | 28 ++++++++-- 5 files changed, 103 insertions(+), 43 deletions(-) diff --git a/HISTORY b/HISTORY index 1fc3bcca..a7b3f08e 100644 --- a/HISTORY +++ b/HISTORY @@ -9,6 +9,7 @@ ENH: Ticket#722 - Add Read Only capabilities to TInPlaceTextBox (Christophe) 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) +ENH: Active Record now supports query criteria for 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 28beaa93..95ce96c7 100644 --- a/demos/quickstart/protected/pages/Database/ActiveRecord.page +++ b/demos/quickstart/protected/pages/Database/ActiveRecord.page @@ -612,7 +612,8 @@ CREATE TABLE bar as an 1-M relationship. That is, one Team may contain 0 or more Players. In terms of object relationships, we say that a TeamRecord object has many PlayerRecord objects. (Notice the reversal of the direction of relationships between tables and objects.) -
+
+We model the Team object as the following Active Record classes. @@ -629,7 +630,7 @@ class TeamRecord extends TActiveRecord //define the $player member having has many relationship with PlayerRecord public static $RELATIONS=array ( - 'players' => array(self::HAS_MANY, 'PlayerRecord'), + 'players' => array(self::HAS_MANY, 'PlayerRecord', 'team_name'), ); public static function finder($className=__CLASS__) @@ -644,10 +645,12 @@ property $players has many PlayerRecords. Multiple rela is permitted by defining each relationship with an entry in the $RELATIONS array where array key for the entry corresponds to the property name. In array(self::HAS_MANY, 'PlayerRecord'), the first element defines the -relationship type, the valid types are self::HAS_MANY, -self::HAS_ONE and self::BELONGS_TO. +relationship type, the valid types are self::HAS_MANY, self::HAS_ONE, +self::BELONGS_TO and self::MANY_TO_MANY. The second element is a string 'PlayerRecord' that corresponds to the class name of the PlayerRecord class. +And the third element 'team_name' refers to the foreign key column in the Players table that +references to the Teams table.
The entity relationship between Players and Profiles is one to one. That is, +each PlayerRecord object has one ProfileRecord object (may be none or null). +A has one relationship is nearly identical to a has many relationship with the exception +that the related object is only one object (not a collection of objects). +
+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) @@ -752,9 +762,9 @@ class PlayerRecord extends TActiveRecord public static $RELATIONS=array ( - 'team' => array(self::BELONGS_TO, 'TeamRecord'), + 'team' => array(self::BELONGS_TO, 'TeamRecord', 'team_name'), 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'), - 'profile' => array(self::HAS_ONE, 'ProfileRecord'), + 'profile' => array(self::HAS_ONE, 'ProfileRecord', 'player_id'), ); public static function finder($className=__CLASS__) @@ -768,10 +778,11 @@ The static $RELATIONS property of PlayerRecord defines that th property $team belongs to a TeamRecord. The $RELATIONS array also defines two other relationships that we shall examine in later sections below. -In array(self::BELONGS_TO, 'TeamRecord'), the first element defines the -relationship type, in this case self::BELONGS_TO and +In array(self::BELONGS_TO, 'TeamRecord', 'team_name'), the first element defines the +relationship type, in this case self::BELONGS_TO; the second element is a string 'TeamRecord' that corresponds to the -class name of the TeamRecord class. +class name of the TeamRecord class; and the third element 'team_name' refers +to the foreign key of Players referencing Teams. A player object with the corresponding team object may be fetched as follows.
The entity relationship between Players and Profiles is one to one. That is, -each PlayerRecord object has one ProfileRecord object (may be none or null). -A has one relationship is nearly identical to a has many relationship with the exception -that the related object is only one object (not a collection of objects). -
-A parent child relationship can be defined using a combination of has many and belongs to relationship that refers to the same class. The following example shows a parent children relationship between @@ -855,12 +859,28 @@ class Category extends TActiveRecord public static $RELATIONS=array ( - 'parent_category' => array(self::BELONGS_TO, 'Category'), - 'child_categories' => array(self::HAS_MANY, 'Category'), + 'parent_category' => array(self::BELONGS_TO, 'Category', 'parent_cat_id'), + 'child_categories' => array(self::HAS_MANY, 'Category', 'parent_cat_id'), ); }
+In the above, we show that an Active Record object can reference to its related objects by +declaring a static class member $RELATIONS which specifies a list of relations. Each relation +is specified as an array consisting of three elements: relation type, related AR class name, +and the foreign key(s). For example, we use array(self::HAS_MANY, 'PlayerRecord', 'team_name') +to specify the players in a team. There are two more optional elements that can be specified +in this array: query condition (the fourth element) and parameters (the fifth element). +They are used to control how to query for the related objects. For example, if we want to obtain +the players ordered by their age, we can specify array(self::HAS_MANY, 'PlayerRecord', 'team_name', 'ORDER BY age'). +If we want to obtain players whose age is smaller than 30, we could use +array(self::HAS_MANY, 'PlayerRecord', 'team_name', 'age<:age', array(':age'=>30)). In general, +these two additional elements are similar as the parameters passed to the find() method in AR. +
+ +diff --git a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php index 28d80683..ff6d9554 100644 --- a/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php +++ b/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php @@ -68,14 +68,6 @@ class TActiveRecordRelationContext return $this->_record; } - /** - * @return string foreign record class name. - */ - public function getForeignRecordClass() - { - return $this->_relation[1]; - } - /** * @return array foreign key of this relations, the keys is dependent on the * relationship type. @@ -96,6 +88,14 @@ class TActiveRecordRelationContext return $this->_relation[0]; } + /** + * @return string foreign record class name. + */ + public function getForeignRecordClass() + { + return $this->_relation[1]; + } + /** * @return string foreign key field names, comma delimited. * @since 3.1.2 @@ -105,6 +105,24 @@ class TActiveRecordRelationContext return $this->_relation[2]; } + /** + * @return string the query condition for the relation as specified in RELATIONS + * @since 3.1.2 + */ + public function getCondition() + { + return isset($this->_relation[3])?$this->_relation[3]:null; + } + + /** + * @return array the query parameters for the relation as specified in RELATIONS + * @since 3.1.2 + */ + public function getParameters() + { + return isset($this->_relation[4])?$this->_relation[4]:array(); + } + /** * @return boolean true if the 3rd element of an TActiveRecord::$RELATION entry is set. * @since 3.1.2 @@ -156,7 +174,7 @@ class TActiveRecordRelationContext $this->_property, get_class($this->_record), 'RELATIONS'); } if($criteria===null) - $criteria = new TActiveRecordCriteria; + $criteria = new TActiveRecordCriteria($this->getCondition(), $this->getParameters()); switch($this->getRelationType()) { case TActiveRecord::HAS_MANY: diff --git a/framework/Data/Common/TDbCommandBuilder.php b/framework/Data/Common/TDbCommandBuilder.php index 0029b4c3..f358036c 100644 --- a/framework/Data/Common/TDbCommandBuilder.php +++ b/framework/Data/Common/TDbCommandBuilder.php @@ -159,10 +159,10 @@ class TDbCommandBuilder extends TComponent */ public function createFindCommand($where='1=1', $parameters=array(), $ordering=array(), $limit=-1, $offset=-1) { - if($where===null) - $where='1=1'; $table = $this->getTableInfo()->getTableFullName(); - $sql = "SELECT * FROM {$table} WHERE {$where}"; + $sql = "SELECT * FROM {$table}"; + if(!empty($where)) + $sql .= " WHERE {$where}"; return $this->applyCriterias($sql, $parameters, $ordering, $limit, $offset); } @@ -185,10 +185,10 @@ class TDbCommandBuilder extends TComponent */ public function createCountCommand($where='1=1', $parameters=array(),$ordering=array(), $limit=-1, $offset=-1) { - if($where===null) - $where='1=1'; $table = $this->getTableInfo()->getTableFullName(); - $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; + $sql = "SELECT COUNT(*) FROM {$table}"; + if(!empty($where)) + $sql .= " WHERE {$where}"; return $this->applyCriterias($sql, $parameters, $ordering, $limit, $offset); } @@ -204,8 +204,8 @@ class TDbCommandBuilder extends TComponent { $table = $this->getTableInfo()->getTableFullName(); if (!empty($where)) - $where = 'WHERE '.$where; - $command = $this->createCommand("DELETE FROM {$table} ".$where); + $where = ' WHERE '.$where; + $command = $this->createCommand("DELETE FROM {$table}".$where); $this->bindArrayValues($command, $parameters); return $command; } @@ -242,7 +242,10 @@ class TDbCommandBuilder extends TComponent $fields = implode(', ', $this->getColumnBindings($data, true)); else $fields = implode(', ', $this->getColumnBindings($data)); - $command = $this->createCommand("UPDATE {$table} SET {$fields} WHERE {$where}"); + + if (!empty($where)) + $where = ' WHERE '.$where; + $command = $this->createCommand("UPDATE {$table} SET {$fields}".$where); $this->bindArrayValues($command, array_merge($data, $parameters)); return $command; } diff --git a/framework/Data/DataGateway/TSqlCriteria.php b/framework/Data/DataGateway/TSqlCriteria.php index d073cd10..a17ac0c6 100644 --- a/framework/Data/DataGateway/TSqlCriteria.php +++ b/framework/Data/DataGateway/TSqlCriteria.php @@ -51,6 +51,7 @@ class TSqlCriteria extends TComponent $this->_parameters->copyFrom((array)$parameters); $this->_ordersBy=new TAttributeCollection; $this->_ordersBy->setCaseSensitive(true); + $this->setCondition($condition); } @@ -68,7 +69,14 @@ class TSqlCriteria extends TComponent */ public function setCondition($value) { - $this->_condition=$value; + if(!empty($value) && preg_match('/ORDER\s+BY\s+(.*?)$/i',$value,$matches)>0) + { + // condition contains ORDER BY, we need to strip it output + $this->_condition=substr($value,0,strpos($value,$matches[0])); + $this->setOrdersBy($matches[1]); + } + else + $this->_condition=$value; } /** @@ -107,13 +115,23 @@ class TSqlCriteria extends TComponent } /** - * @param ArrayAccess ordering clause. + * @param mixed ordering clause. */ public function setOrdersBy($value) { - if(!(is_array($value) || $value instanceof ArrayAccess)) - throw new TException('value must be array or ArrayAccess'); - $this->_ordersBy->copyFrom($value); + if(is_array($value) || $value instanceof Traversable) + $this->_ordersBy->copyFrom($value); + else + { + $value=trim(preg_replace('/\s+/',' ',(string)$value)); + $orderBys=array(); + foreach(explode(',',$value) as $orderBy) + { + $vs=explode(' ',trim($orderBy)); + $orderBys[$vs[0]]=isset($vs[1])?$vs[1]:'asc'; + } + $this->_ordersBy->copyFrom($orderBys); + } } /** -- cgit v1.2.3