Changeset 2309
- Timestamp:
- 10/07/2007 10:24:07 PM (15 months ago)
- Location:
- trunk
- Files:
-
- 3 added
- 17 modified
-
UPGRADE (modified) (1 diff)
-
demos/northwind-db/protected/database/Employee.php (modified) (1 diff)
-
demos/quickstart/protected/pages/Database/ActiveRecord.page (modified) (6 diffs)
-
demos/quickstart/protected/pages/Database/ar_objects.png (modified) (previous)
-
demos/quickstart/protected/pages/Database/ar_objects.vsd (modified) (previous)
-
demos/quickstart/protected/pages/Database/id/ActiveRecord.page (modified) (4 diffs)
-
framework/Caching/TXCache.php (added)
-
framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php (modified) (2 diffs)
-
framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php (modified) (2 diffs)
-
framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php (modified) (5 diffs)
-
framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php (modified) (2 diffs)
-
framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php (modified) (6 diffs)
-
framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php (modified) (9 diffs)
-
framework/Data/ActiveRecord/TActiveRecord.php (modified) (3 diffs)
-
tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php (modified) (4 diffs)
-
tests/simple_unit/ActiveRecord/ForeignObjectUpdateTest.php (modified) (3 diffs)
-
tests/simple_unit/ActiveRecord/MultipleForeignKeyTestCase.php (added)
-
tests/simple_unit/ActiveRecord/records/ItemRecord.php (modified) (1 diff)
-
tests/simple_unit/ActiveRecord/test1.sqlite (added)
-
tests/test_tools/simpletest/test_case.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/UPGRADE
r2283 r2309 12 12 Upgrading from v3.1.1 13 13 --------------------- 14 - The RELATIONS type declaration in Active Record classes for Many-to-Many using 15 an association table was change from "self::HAS_MANY" to "self::MANY_TO_MANY". 16 E.g. change 17 'albums' => array(self::HAS_MANY, 'Artist', 'album_artists') 18 to 19 'albums' => array(self::MANY_TO_MANY, 'Artist', 'album_artists') 20 14 21 15 22 Upgrading from v3.1.0 -
trunk/demos/northwind-db/protected/database/Employee.php
r2274 r2309 33 33 public static $RELATIONS = array 34 34 ( 35 'Territories' => array(self:: HAS_MANY, 'Territory', 'EmployeeTerritories'),35 'Territories' => array(self::MANY_TO_MANY, 'Territory', 'EmployeeTerritories'), 36 36 'Orders' => array(self::HAS_MANY, 'Order'), 37 37 -
trunk/demos/quickstart/protected/pages/Database/ActiveRecord.page
r2274 r2309 658 658 659 659 <div class="info"><b class="note">Info:</b> 660 Active Record supports multiple table foreign key relationships with the restriction 661 that each relationship corresponds to a unique table. For example, the <tt>Players</tt> 662 table may only have one set of foreign key relationship with table <tt>Teams</tt>, it may 663 have other relationships that corresponds to other tables (including the <tt>Players</tt> table itself). 660 Since version <b>3.1.2</b>, Active Record supports multiple foreign key 661 references of the same table. Ambiguity between multiple foreign key references to the same table is 662 resolved by providing the foreign key column name as the 3rd parameter in the relationship array. 663 For example, both the following foreign keys <tt>owner_id</tt> and <tt>reporter_id</tt> 664 references the same table defined in <tt>UserRecord</tt>. 665 <com:TTextHighlighter Language="php" CssClass="source block-content"> 666 class TicketRecord extends TActiveRecord 667 { 668 public $owner_id; 669 public $reporter_id; 670 671 public $owner; 672 public $reporter; 673 674 public static $RELATION=array 675 ( 676 'owner' => array(self::BELONGS_TO, 'UserRecord', 'owner_id'), 677 'reporter' => array(self::BELONGS_TO, 'UserRecord', 'reporter_id'), 678 ); 679 } 680 </com:TTextHighlighter> 681 This is applicable to relationships including <tt>BELONGS_TO</tt>, <tt>HAS_ONE</tt> and 682 <tt>HAS_MANY</tt>. See section <a href="#142021">Self Referenced Association Tables</a> for solving ambiguity of <tt>MANY_TO_MANY</tt> 683 relationships. 664 684 </div> 665 685 … … 714 734 ( 715 735 'team' => array(self::BELONGS_TO, 'TeamRecord'), 716 'skills' => array(self:: HAS_MANY, 'SkillRecord', 'Player_Skills'),736 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'), 717 737 'profile' => array(self::HAS_ONE, 'ProfileRecord'), 718 738 ); … … 851 871 852 872 <p id="710035" class="block-content">The Prado Active Record design implements the two stage approach. For the 853 <tt>Players</tt>-<tt>Skills</tt> M-N (many-to-many) entity relationship, we need854 to define a <b>hasmany</b> relationship in the <tt>PlayerRecord</tt> class and855 in addition define a <b>hasmany</b> relationship in the <tt>SkillRecord</tt> class as well.873 <tt>Players</tt>-<tt>Skills</tt> M-N (many-to-many) entity relationship, we 874 define a <b>many-to-many</b> relationship in the <tt>PlayerRecord</tt> class and 875 in addition we may define a <b>many-to-many</b> relationship in the <tt>SkillRecord</tt> class as well. 856 876 The following sample code defines the complete <tt>SkillRecord</tt> class with a 857 877 many-to-many relationship with the <tt>PlayerRecord</tt> class. (See the <tt>PlayerRecord</tt> … … 870 890 public static $RELATIONS=array 871 891 ( 872 'players' => array(self:: HAS_MANY, 'PlayerRecord', 'Player_Skills'),892 'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'), 873 893 ); 874 894 … … 883 903 The static <tt>$RELATIONS</tt> property of SkillRecord defines that the 884 904 property <tt>$players</tt> has many <tt>PlayerRecord</tt>s via an association table '<tt>Player_Skills</tt>'. 885 In <tt>array(self:: HAS_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the886 relationship type, in this case <strong><tt>self:: HAS_MANY</tt></strong>,905 In <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the 906 relationship type, in this case <strong><tt>self::MANY_TO_MANY</tt></strong>, 887 907 the second element is a string <tt>'PlayerRecord'</tt> that corresponds to the 888 908 class name of the <tt>PlayerRecord</tt> class, and the third element is the name 889 909 of the association table name. 890 910 </p> 911 912 <div class="note"><b class="note">Note:</b> 913 Prior to version <b>3.1.2</b> (versions up to 3.1.1), the many-to-many relationship was 914 defined using <tt>self::HAS_MANY</tt>. For version <b>3.1.2</b> onwards, this must be changed 915 to <tt>self::MANY_TO_MANY</tt>. This can be done by searching for the <tt>HAS_MANY</tt> in your 916 source code and carfully changing the appropriate definitions. 917 </div> 918 891 919 <p id="710037" class="block-content"> 892 920 A list of player objects with the corresponding collection of skill objects may be fetched as follows. … … 952 980 public static $RELATIONS=array 953 981 ( 954 'related_items' => array(self:: HAS_MANY,982 'related_items' => array(self::MANY_TO_MANY, 955 983 'Item', 'related_items.related_item_id'), 956 984 ); -
trunk/demos/quickstart/protected/pages/Database/id/ActiveRecord.page
r2274 r2309 616 616 ( 617 617 'team' => array(self::BELONGS_TO, 'TeamRecord'), 618 'skills' => array(self:: HAS_MANY, 'SkillRecord', 'Player_Skills'),618 'skills' => array(self::MANY_TO_MANY, 'SkillRecord', 'Player_Skills'), 619 619 'profile' => array(self::HAS_ONE, 'ProfileRecord'), 620 620 ); … … 738 738 public static $RELATIONS=array 739 739 ( 740 'players' => array(self:: HAS_MANY, 'PlayerRecord', 'Player_Skills'),740 'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'), 741 741 ); 742 742 … … 750 750 <p id="710036" class="block-content"> 751 751 Properti statis <tt>$RELATIONS</tt> dari SkillRecord mendefinisikan bahwa properti <tt>$players</tt> memiliki banyak <tt>PlayerRecord</tt>s melalui tabel asosiasi '<tt>Player_Skills</tt>'. 752 Dalam <tt>array(self:: HAS_MANY, 'PlayerRecord', 'Player_Skills')</tt>, elemen pertama mendefinisikan tipe hubungan, dalam hal ini <strong><tt>self::HAS_MANY</tt></strong>,752 Dalam <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, elemen pertama mendefinisikan tipe hubungan, dalam hal ini <strong><tt>self::HAS_MANY</tt></strong>, 753 753 elemen kedua adalah string <tt>'PlayerRecord'</tt> yang terkait ke nama kelas dari kelas <tt>PlayerRecord</tt>, dan elemen ketiga adalah nama dari nama tabel asosiasi. 754 754 </p> … … 806 806 public static $RELATIONS=array 807 807 ( 808 'related_items' => array(self:: HAS_MANY,808 'related_items' => array(self::MANY_TO_MANY, 809 809 'Item', 'related_items.related_item_id'), 810 810 ); -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php
r2275 r2309 78 78 protected function collectForeignObjects(&$results) 79 79 { 80 $fkObject = $this->getContext()->getForeignRecordFinder(); 81 $fkeys = $this->findForeignKeys($this->getSourceRecord(),$fkObject); 80 $fkeys = $this->getRelationForeignKeys(); 82 81 83 82 $properties = array_keys($fkeys); … … 87 86 $fkObjects = $this->findForeignObjects($fields, $indexValues); 88 87 $this->populateResult($results,$properties,$fkObjects,$fields); 88 } 89 90 /** 91 * @return array foreign key field names as key and object properties as value. 92 * @since 3.1.2 93 */ 94 public function getRelationForeignKeys() 95 { 96 $fkObject = $this->getContext()->getForeignRecordFinder(); 97 return $this->findForeignKeys($this->getSourceRecord(),$fkObject); 89 98 } 90 99 -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php
r2275 r2309 76 76 protected function collectForeignObjects(&$results) 77 77 { 78 $fkObject = $this->getContext()->getForeignRecordFinder(); 79 $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord()); 78 $fkeys = $this->getRelationForeignKeys(); 80 79 81 80 $properties = array_values($fkeys); … … 85 84 $fkObjects = $this->findForeignObjects($fields,$indexValues); 86 85 $this->populateResult($results,$properties,$fkObjects,$fields); 86 } 87 88 /** 89 * @return array foreign key field names as key and object properties as value. 90 * @since 3.1.2 91 */ 92 public function getRelationForeignKeys() 93 { 94 $fkObject = $this->getContext()->getForeignRecordFinder(); 95 return $this->findForeignKeys($fkObject, $this->getSourceRecord()); 87 96 } 88 97 -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php
r2275 r2309 38 38 * public static $RELATIONS = array 39 39 * ( 40 * 'Categories' => array(self:: HAS_MANY, 'CategoryRecord', 'Article_Category')40 * 'Categories' => array(self::MANY_TO_MANY, 'CategoryRecord', 'Article_Category') 41 41 * ); 42 42 * … … 55 55 * public static $RELATIONS = array 56 56 * ( 57 * 'Articles' => array(self:: HAS_MANY, 'ArticleRecord', 'Article_Category')57 * 'Articles' => array(self::MANY_TO_MANY, 'ArticleRecord', 'Article_Category') 58 58 * ); 59 59 * … … 97 97 protected function collectForeignObjects(&$results) 98 98 { 99 list($sourceKeys, $foreignKeys) = $this->getRelationForeignKeys(); 100 $properties = array_values($sourceKeys); 101 $indexValues = $this->getIndexValues($properties, $results); 102 $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys); 103 } 104 105 /** 106 * @return array 2 arrays of source keys and foreign keys from the association table. 107 */ 108 public function getRelationForeignKeys() 109 { 99 110 $association = $this->getAssociationTable(); 100 $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord()); 101 102 $properties = array_values($sourceKeys); 103 104 $indexValues = $this->getIndexValues($properties, $results); 105 111 $sourceKeys = $this->findForeignKeys($association, $this->getSourceRecord(), true); 106 112 $fkObject = $this->getContext()->getForeignRecordFinder(); 107 113 $foreignKeys = $this->findForeignKeys($association, $fkObject); 108 109 $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys); 114 return array($sourceKeys, $foreignKeys); 110 115 } 111 116 … … 183 188 protected function fetchForeignObjects(&$results,$foreignKeys,$indexValues,$sourceKeys) 184 189 { 185 $criteria = $this->getC ontext()->getCriteria();190 $criteria = $this->getCriteria(); 186 191 $finder = $this->getContext()->getForeignRecordFinder(); 187 192 $registry = $finder->getRecordManager()->getObjectStateRegistry(); … … 199 204 $registry->registerClean($obj); 200 205 } 201 202 206 $this->setResultCollection($results, $collections, array_values($sourceKeys)); 203 207 } -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php
r2275 r2309 93 93 protected function collectForeignObjects(&$results) 94 94 { 95 $fkObject = $this->getContext()->getForeignRecordFinder(); 96 $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord()); 97 95 $fkeys = $this->getRelationForeignKeys(); 98 96 $properties = array_values($fkeys); 99 97 $fields = array_keys($fkeys); … … 102 100 $fkObjects = $this->findForeignObjects($fields,$indexValues); 103 101 $this->populateResult($results,$properties,$fkObjects,$fields); 102 } 103 104 /** 105 * @return array foreign key field names as key and object properties as value. 106 * @since 3.1.2 107 */ 108 public function getRelationForeignKeys() 109 { 110 $fkObject = $this->getContext()->getForeignRecordFinder(); 111 return $this->findForeignKeys($fkObject, $this->getSourceRecord()); 104 112 } 105 113 -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
r2275 r2309 27 27 { 28 28 private $_context; 29 30 public function __construct(TActiveRecordRelationContext $context) 29 private $_criteria; 30 31 public function __construct(TActiveRecordRelationContext $context, $criteria) 31 32 { 32 33 $this->_context = $context; 34 $this->_criteria = $criteria; 33 35 } 34 36 … … 39 41 { 40 42 return $this->_context; 43 } 44 45 /** 46 * @return TActiveRecordCriteria 47 */ 48 protected function getCriteria() 49 { 50 return $this->_criteria; 41 51 } 42 52 … … 76 86 return $results; 77 87 } 88 89 /** 90 * Fetch results for current relationship. 91 * @return boolean always true. 92 */ 93 public function fetchResultsInto($obj) 94 { 95 $this->collectForeignObjects($obj); 96 return true; 97 } 78 98 79 99 /** … … 85 105 * @return array foreign keys with source column names as key and foreign column names as value. 86 106 */ 87 protected function findForeignKeys($from, $matchesRecord )107 protected function findForeignKeys($from, $matchesRecord, $loose=false) 88 108 { 89 109 $gateway = $matchesRecord->getRecordGateway(); … … 94 114 foreach($tableInfo->getForeignKeys() as $fkeys) 95 115 { 96 if($fkeys['table']===$matchingTableName) 97 return $fkeys['keys']; 116 if(strtolower($fkeys['table'])===strtolower($matchingTableName)) 117 { 118 if(!$loose && $this->getContext()->hasFkField()) 119 return $this->getFkFields($fkeys['keys']); 120 else 121 return $fkeys['keys']; 122 } 98 123 } 99 124 $matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName(); 100 125 throw new TActiveRecordException('ar_relations_missing_fk', 101 126 $tableInfo->getTableFullName(), $matching); 127 } 128 129 /** 130 * @return array foreign key field names as key and object properties as value. 131 * @since 3.1.2 132 */ 133 abstract public function getRelationForeignKeys(); 134 135 /** 136 * Find matching foreign key fields from the 3rd element of an entry in TActiveRecord::$RELATION. 137 * Assume field names consist of [\w-] character sets. Prefix to the field names ending with a dot 138 * are ignored. 139 */ 140 private function getFkFields($fkeys) 141 { 142 $matching = array(); 143 preg_match_all('/\s*(\S+\.)?([\w-]+)\s*/', $this->getContext()->getFkField(), $matching); 144 $fields = array(); 145 foreach($fkeys as $fkName => $field) 146 { 147 if(in_array($fkName, $matching[2])) 148 $fields[$fkName] = $field; 149 } 150 return $fields; 102 151 } 103 152 … … 123 172 protected function findForeignObjects($fields, $indexValues) 124 173 { 125 $criteria = $this->getContext()->getCriteria();126 174 $finder = $this->getContext()->getForeignRecordFinder(); 127 return $finder->findAllByIndex($ criteria, $fields, $indexValues);175 return $finder->findAllByIndex($this->_criteria, $fields, $indexValues); 128 176 } 129 177 -
trunk/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php
r2275 r2309 33 33 private $_property; 34 34 private $_sourceRecord; 35 private $_ criteria;36 private $_ relation;37 38 public function __construct($source, $property=null , $criteria=null)35 private $_relation; //data from an entry of TActiveRecord::$RELATION 36 private $_fkeys; 37 38 public function __construct($source, $property=null) 39 39 { 40 40 $this->_sourceRecord=$source; 41 $this->_criteria=$criteria;42 41 if($property!==null) 43 42 list($this->_property, $this->_relation) = $this->getSourceRecordRelation($property); … … 49 48 * @param string relation property name 50 49 * @return array array($propertyName, $relation) relation definition. 51 * @throws TActiveRecordException if property is not defined or missing.52 50 */ 53 51 protected function getSourceRecordRelation($property) … … 59 57 return array($name, $relation); 60 58 } 61 throw new TActiveRecordException('ar_undefined_relation_prop',62 $property, get_class($this->_sourceRecord), self::RELATIONS_CONST);63 59 } 64 60 … … 72 68 } 73 69 70 /** 71 * @return boolean true if the relation is defined in TActiveRecord::$RELATIONS 72 * @since 3.1.2 73 */ 74 public function hasRecordRelation() 75 { 76 return $this->_relation!==null; 77 } 78 74 79 public function getPropertyValue() 75 80 { … … 87 92 88 93 /** 89 * @return TActiveRecordCriteria sql query criteria for fetching the related record.90 */91 public function getCriteria()92 {93 return $this->_criteria;94 }95 96 /**97 94 * @return TActiveRecord the active record instance that queried for its related records. 98 95 */ … … 111 108 112 109 /** 110 * @return array foreign key of this relations, the keys is dependent on the 111 * relationship type. 112 * @since 3.1.2 113 */ 114 public function getRelationForeignKeys() 115 { 116 if($this->_fkeys===null) 117 $this->_fkeys=$this->getRelationHandler()->getRelationForeignKeys(); 118 return $this->_fkeys; 119 } 120 121 /** 113 122 * @return string HAS_MANY, HAS_ONE, or BELONGS_TO 114 123 */ … … 119 128 120 129 /** 130 * @return string foreign key field names, comma delimited. 131 * @since 3.1.2 132 */ 133 public function getFkField() 134 { 135 return $this->_relation[2]; 136 } 137 138 /** 139 * @return boolean true if the 3rd element of an TActiveRecord::$RELATION entry is set. 140 * @since 3.1.2 141 */ 142 public function hasFkField() 143 { 144 $notManyToMany = $this->getRelationType() !== TActiveRecord::MANY_TO_MANY; 145 return $notManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]); 146 } 147 148 /** 121 149 * @return string the M-N relationship association table name. 122 150 */ … … 131 159 public function hasAssociationTable() 132 160 { 133 return isset($this->_relation[2]); 161 $isManyToMany = $this->getRelationType() === TActiveRecord::MANY_TO_MANY; 162 return $isManyToMany && isset($this->_relation[2]) && !empty($this->_relation[2]); 134 163 } 135 164 … … 146 175 * An instance of TActiveRecordHasOne, TActiveRecordBelongsTo, TActiveRecordHasMany, 147 176 * or TActiveRecordHasManyAssocation will be returned. 177 * @param TActiveRecordCriteria search criteria 148 178 * @return TActiveRecordRelation record relationship handler instnace. 149 */ 150 public function getRelationHandler() 151 { 179 * @throws TActiveRecordException if property is not defined or missing. 180 */ 181 public function getRelationHandler($criteria=null) 182 { 183 if(!$this->hasRecordRelation()) 184 { 185 throw new TActiveRecordException('ar_undefined_relation_prop', 186 $property, get_class($this->_sourceRecord), self::RELATIONS_CONST); 187 } 188 if($criteria===null) 189 $criteria = new TActiveRecordCriteria; 152 190 switch($this->getRelationType()) 153 191 { 154 192 case TActiveRecord::HAS_MANY: 155 if(!$this->hasAssociationTable()) 156 { 157 Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany'); 158 return new TActiveRecordHasMany($this); 159 &nb
