Changeset 2309

Show
Ignore:
Timestamp:
10/07/2007 10:24:07 PM (15 months ago)
Author:
wei
Message:

Allow active records to have multiple foreign key references to the same table. Add TXCache.

Location:
trunk
Files:
3 added
17 modified

Legend:

Unmodified
Added
Removed
  • trunk/UPGRADE

    r2283 r2309  
    1212Upgrading from v3.1.1 
    1313--------------------- 
     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 
    1421 
    1522Upgrading from v3.1.0 
  • trunk/demos/northwind-db/protected/database/Employee.php

    r2274 r2309  
    3333        public static $RELATIONS = array 
    3434        ( 
    35                 'Territories' => array(self::HAS_MANY, 'Territory', 'EmployeeTerritories'), 
     35                'Territories' => array(self::MANY_TO_MANY, 'Territory', 'EmployeeTerritories'), 
    3636                'Orders' => array(self::HAS_MANY, 'Order'), 
    3737 
  • trunk/demos/quickstart/protected/pages/Database/ActiveRecord.page

    r2274 r2309  
    658658 
    659659<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). 
     660Since version <b>3.1.2</b>, Active Record supports multiple foreign key  
     661references of the same table. Ambiguity between multiple foreign key references to the same table is 
     662resolved by providing the foreign key column name as the 3rd parameter in the relationship array.  
     663For example, both the following foreign keys <tt>owner_id</tt> and <tt>reporter_id</tt> 
     664references the same table defined in <tt>UserRecord</tt>. 
     665<com:TTextHighlighter Language="php" CssClass="source block-content"> 
     666class 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> 
     681This 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>  
     683relationships. 
    664684</div> 
    665685 
     
    714734    ( 
    715735        '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'), 
    717737        'profile' => array(self::HAS_ONE, 'ProfileRecord'), 
    718738    ); 
     
    851871 
    852872<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 need 
    854 to define a <b>has many</b> relationship in the <tt>PlayerRecord</tt> class and 
    855 in addition define a <b>has many</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  
     874define a <b>many-to-many</b> relationship in the <tt>PlayerRecord</tt> class and 
     875in addition we may define a <b>many-to-many</b> relationship in the <tt>SkillRecord</tt> class as well. 
    856876The following sample code defines the complete <tt>SkillRecord</tt> class with a 
    857877many-to-many relationship with the <tt>PlayerRecord</tt> class. (See the <tt>PlayerRecord</tt> 
     
    870890    public static $RELATIONS=array 
    871891    ( 
    872         'players' => array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills'), 
     892        'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'), 
    873893    ); 
    874894 
     
    883903The static <tt>$RELATIONS</tt> property of SkillRecord defines that the 
    884904property <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 the 
    886 relationship type, in this case <strong><tt>self::HAS_MANY</tt></strong>, 
     905In <tt>array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills')</tt>, the first element defines the 
     906relationship type, in this case <strong><tt>self::MANY_TO_MANY</tt></strong>, 
    887907the second element is a string <tt>'PlayerRecord'</tt> that corresponds to the 
    888908class name of the <tt>PlayerRecord</tt> class, and the third element is the name 
    889909of the association table name. 
    890910</p> 
     911 
     912<div class="note"><b class="note">Note:</b> 
     913Prior to version <b>3.1.2</b> (versions up to 3.1.1), the many-to-many relationship was  
     914defined using <tt>self::HAS_MANY</tt>. For version <b>3.1.2</b> onwards, this must be changed 
     915to <tt>self::MANY_TO_MANY</tt>. This can be done by searching for the <tt>HAS_MANY</tt> in your 
     916source code and carfully changing the appropriate definitions.  
     917</div> 
     918 
    891919<p id="710037" class="block-content"> 
    892920A list of player objects with the corresponding collection of skill objects may be fetched as follows. 
     
    952980    public static $RELATIONS=array 
    953981    ( 
    954         'related_items' => array(self::HAS_MANY, 
     982        'related_items' => array(self::MANY_TO_MANY, 
    955983            'Item', 'related_items.related_item_id'), 
    956984    ); 
  • trunk/demos/quickstart/protected/pages/Database/id/ActiveRecord.page

    r2274 r2309  
    616616    ( 
    617617        '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'), 
    619619        'profile' => array(self::HAS_ONE, 'ProfileRecord'), 
    620620    ); 
     
    738738    public static $RELATIONS=array 
    739739    ( 
    740         'players' => array(self::HAS_MANY, 'PlayerRecord', 'Player_Skills'), 
     740        'players' => array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'), 
    741741    ); 
    742742 
     
    750750<p id="710036" class="block-content"> 
    751751Properti 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>, 
     752Dalam <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>, 
    753753elemen 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.  
    754754</p> 
     
    806806    public static $RELATIONS=array 
    807807    ( 
    808         'related_items' => array(self::HAS_MANY,  
     808        'related_items' => array(self::MANY_TO_MANY,  
    809809            'Item', 'related_items.related_item_id'), 
    810810    ); 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordBelongsTo.php

    r2275 r2309  
    7878        protected function collectForeignObjects(&$results) 
    7979        { 
    80                 $fkObject = $this->getContext()->getForeignRecordFinder(); 
    81                 $fkeys = $this->findForeignKeys($this->getSourceRecord(),$fkObject); 
     80                $fkeys = $this->getRelationForeignKeys(); 
    8281 
    8382                $properties = array_keys($fkeys); 
     
    8786                $fkObjects = $this->findForeignObjects($fields, $indexValues); 
    8887                $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);               
    8998        } 
    9099 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasMany.php

    r2275 r2309  
    7676        protected function collectForeignObjects(&$results) 
    7777        { 
    78                 $fkObject = $this->getContext()->getForeignRecordFinder(); 
    79                 $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord()); 
     78                $fkeys = $this->getRelationForeignKeys(); 
    8079 
    8180                $properties = array_values($fkeys); 
     
    8584                $fkObjects = $this->findForeignObjects($fields,$indexValues); 
    8685                $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()); 
    8796        } 
    8897 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasManyAssociation.php

    r2275 r2309  
    3838 *     public static $RELATIONS = array 
    3939 *     ( 
    40  *         'Categories' => array(self::HAS_MANY, 'CategoryRecord', 'Article_Category') 
     40 *         'Categories' => array(self::MANY_TO_MANY, 'CategoryRecord', 'Article_Category') 
    4141 *     ); 
    4242 * 
     
    5555 *     public static $RELATIONS = array 
    5656 *     ( 
    57  *         'Articles' => array(self::HAS_MANY, 'ArticleRecord', 'Article_Category') 
     57 *         'Articles' => array(self::MANY_TO_MANY, 'ArticleRecord', 'Article_Category') 
    5858 *     ); 
    5959 * 
     
    9797        protected function collectForeignObjects(&$results) 
    9898        { 
     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        { 
    99110                $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); 
    106112                $fkObject = $this->getContext()->getForeignRecordFinder(); 
    107113                $foreignKeys = $this->findForeignKeys($association, $fkObject); 
    108  
    109                 $this->fetchForeignObjects($results, $foreignKeys,$indexValues,$sourceKeys); 
     114                return array($sourceKeys, $foreignKeys); 
    110115        } 
    111116 
     
    183188        protected function fetchForeignObjects(&$results,$foreignKeys,$indexValues,$sourceKeys) 
    184189        { 
    185                 $criteria = $this->getContext()->getCriteria(); 
     190                $criteria = $this->getCriteria(); 
    186191                $finder = $this->getContext()->getForeignRecordFinder(); 
    187192                $registry = $finder->getRecordManager()->getObjectStateRegistry(); 
     
    199204                        $registry->registerClean($obj); 
    200205                } 
    201  
    202206                $this->setResultCollection($results, $collections, array_values($sourceKeys)); 
    203207        } 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordHasOne.php

    r2275 r2309  
    9393        protected function collectForeignObjects(&$results) 
    9494        { 
    95                 $fkObject = $this->getContext()->getForeignRecordFinder(); 
    96                 $fkeys = $this->findForeignKeys($fkObject, $this->getSourceRecord()); 
    97  
     95                $fkeys = $this->getRelationForeignKeys(); 
    9896                $properties = array_values($fkeys); 
    9997                $fields = array_keys($fkeys); 
     
    102100                $fkObjects = $this->findForeignObjects($fields,$indexValues); 
    103101                $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()); 
    104112        } 
    105113 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php

    r2275 r2309  
    2727{ 
    2828        private $_context; 
    29  
    30         public function __construct(TActiveRecordRelationContext $context) 
     29        private $_criteria; 
     30 
     31        public function __construct(TActiveRecordRelationContext $context, $criteria) 
    3132        { 
    3233                $this->_context = $context; 
     34                $this->_criteria = $criteria; 
    3335        } 
    3436 
     
    3941        { 
    4042                return $this->_context; 
     43        } 
     44         
     45        /** 
     46         * @return TActiveRecordCriteria 
     47         */ 
     48        protected function getCriteria() 
     49        { 
     50                return $this->_criteria; 
    4151        } 
    4252 
     
    7686                return $results; 
    7787        } 
     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        } 
    7898 
    7999        /** 
     
    85105         * @return array foreign keys with source column names as key and foreign column names as value. 
    86106         */ 
    87         protected function findForeignKeys($from, $matchesRecord) 
     107        protected function findForeignKeys($from, $matchesRecord, $loose=false) 
    88108        { 
    89109                $gateway = $matchesRecord->getRecordGateway(); 
     
    94114                foreach($tableInfo->getForeignKeys() as $fkeys) 
    95115                { 
    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                        } 
    98123                } 
    99124                $matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName(); 
    100125                throw new TActiveRecordException('ar_relations_missing_fk', 
    101126                        $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; 
    102151        } 
    103152 
     
    123172        protected function findForeignObjects($fields, $indexValues) 
    124173        { 
    125                 $criteria = $this->getContext()->getCriteria(); 
    126174                $finder = $this->getContext()->getForeignRecordFinder(); 
    127                 return $finder->findAllByIndex($criteria, $fields, $indexValues); 
     175                return $finder->findAllByIndex($this->_criteria, $fields, $indexValues); 
    128176        } 
    129177 
  • trunk/framework/Data/ActiveRecord/Relations/TActiveRecordRelationContext.php

    r2275 r2309  
    3333        private $_property; 
    3434        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) 
    3939        { 
    4040                $this->_sourceRecord=$source; 
    41                 $this->_criteria=$criteria; 
    4241                if($property!==null) 
    4342                        list($this->_property, $this->_relation) = $this->getSourceRecordRelation($property); 
     
    4948         * @param string relation property name 
    5049         * @return array array($propertyName, $relation) relation definition. 
    51          * @throws TActiveRecordException if property is not defined or missing. 
    5250         */ 
    5351        protected function getSourceRecordRelation($property) 
     
    5957                                return array($name, $relation); 
    6058                } 
    61                 throw new TActiveRecordException('ar_undefined_relation_prop', 
    62                         $property, get_class($this->_sourceRecord), self::RELATIONS_CONST); 
    6359        } 
    6460 
     
    7268        } 
    7369 
     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 
    7479        public function getPropertyValue() 
    7580        { 
     
    8792 
    8893        /** 
    89          * @return TActiveRecordCriteria sql query criteria for fetching the related record. 
    90          */ 
    91         public function getCriteria() 
    92         { 
    93                 return $this->_criteria; 
    94         } 
    95  
    96         /** 
    9794         * @return TActiveRecord the active record instance that queried for its related records. 
    9895         */ 
     
    111108 
    112109        /** 
     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        /** 
    113122         * @return string HAS_MANY, HAS_ONE, or BELONGS_TO 
    114123         */ 
     
    119128 
    120129        /** 
     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        /** 
    121149         * @return string the M-N relationship association table name. 
    122150         */ 
     
    131159        public function hasAssociationTable() 
    132160        { 
    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]); 
    134163        } 
    135164 
     
    146175         * An instance of TActiveRecordHasOne, TActiveRecordBelongsTo, TActiveRecordHasMany, 
    147176         * or TActiveRecordHasManyAssocation will be returned. 
     177         * @param TActiveRecordCriteria search criteria 
    148178         * @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; 
    152190                switch($this->getRelationType()) 
    153191                { 
    154192                        case TActiveRecord::HAS_MANY: 
    155                                 if(!$this->hasAssociationTable()) 
    156                                 { 
    157                                         Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordHasMany'); 
    158                                         return new TActiveRecordHasMany($this); 
    159                 &nb