| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4 // +----------------------------------------------------------------------+ 5 // | Akelos Framework - http://www.akelos.org | 6 // +----------------------------------------------------------------------+ 7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez | 8 // | Released under the GNU Lesser General Public License, see LICENSE.txt| 9 // +----------------------------------------------------------------------+ 10 11 /** 12 * @package ActiveRecord 13 * @subpackage Base 14 * @author Bermi Ferrer <bermi a.t akelos c.om> 15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org 16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 17 */ 18 19 require_once (AK_LIB_DIR.DS.'AkBaseModel.php'); 20 21 /** 22 Adds the following methods for retrieval and query of a single associated object. association is replaced with the symbol passed as the first argument, so has_one :manager would add among others manager.nil?. 23 24 * association(force_reload = false) - returns the associated object. Nil is returned if none is found. 25 * association=(associate) - assigns the associate object, extracts the primary key, sets it as the foreign key, and saves the associate object. 26 * association.nil? - returns true if there is no associated object. 27 * build_association(attributes = {}) - returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if an association already exists. It will NOT work if the association is nil. 28 * create_association(attributes = {}) - returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key and that has already been saved (if it passed the validation). 29 30 Example: An Account class declares has_one :beneficiary, which will add: 31 32 * Account#beneficiary (similar to Beneficiary.find(:first, :conditions => "account_id = #{id}")) 33 * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) 34 * Account#beneficiary.nil? 35 * Account#build_beneficiary (similar to Beneficiary.new("account_id" => id)) 36 * Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b) 37 */ 38 39 40 class AkAssociatedActiveRecord extends AkBaseModel 41 { 42 var $__activeRecordObject = false; 43 var $_AssociationHandler; 44 var $_associationId = false; 45 // Holds different association IDs related to this model 46 var $_associationIds = array(); 47 var $_associations = array(); 48 49 function _loadAssociationHandler($association_type) 50 { 51 if(empty($this->$association_type) && in_array($association_type, array('hasOne','belongsTo','hasMany','hasAndBelongsToMany'))){ 52 $association_handler_class_name = 'Ak'.ucfirst($association_type); 53 require_once(AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkAssociations'.DS.$association_handler_class_name.'.php'); 54 $this->$association_type =& new $association_handler_class_name($this); 55 } 56 return !empty($this->$association_type); 57 } 58 59 function setAssociationHandler(&$AssociationHandler, $association_id) 60 { 61 $this->_AssociationHandler =& $AssociationHandler; 62 } 63 64 function loadAssociations() 65 { 66 $association_aliases = array( 67 'hasOne' => array('hasOne','has_one'), 68 'belongsTo' => array('belongsTo','belongs_to'), 69 'hasMany' => array('hasMany','has_many'), 70 'hasAndBelongsToMany' => array('hasAndBelongsToMany', 'habtm', 'has_and_belongs_to_many'), 71 ); 72 73 foreach ($association_aliases as $association_type=>$aliases){ 74 $association_details = false; 75 foreach ($aliases as $alias){ 76 if(empty($association_details) && !empty($this->$alias)){ 77 $association_details = $this->$alias; 78 } 79 unset($this->$alias); 80 } 81 if(!empty($association_details) && $this->_loadAssociationHandler($association_type)){ 82 $this->$association_type->initializeAssociated($association_details); 83 $this->_associations[$association_type] =& $this->$association_type; 84 } 85 } 86 } 87 88 /** 89 * Gets an array of associated object of selected association type. 90 */ 91 function &getAssociated($association_type) 92 { 93 $result = array(); 94 if(!empty($this->$association_type) && in_array($association_type, array('hasOne','belongsTo','hasMany','hasAndBelongsToMany'))){ 95 $result =& $this->$association_type->getModels(); 96 } 97 return $result; 98 } 99 100 function getId() 101 { 102 return false; 103 } 104 105 106 function &assign(&$Associated) 107 { 108 $result = false; 109 if(is_object($this->_AssociationHandler)){ 110 $result =& $this->_AssociationHandler->assign($this->getAssociationId(), $Associated); 111 } 112 return $result; 113 } 114 115 /** 116 * Returns a new object of the associated type that has been instantiated with attributes 117 * and linked to this object through a foreign key but has not yet been saved. 118 */ 119 function &build($attributes = array(), $replace_existing = true) 120 { 121 $result = false; 122 if(!empty($this->_AssociationHandler)){ 123 $result =& $this->_AssociationHandler->build($this->getAssociationId(), $attributes, $replace_existing); 124 } 125 return $result; 126 } 127 128 129 function &create($attributes = array(), $replace_existing = true) 130 { 131 $result = false; 132 if(!empty($this->_AssociationHandler)){ 133 $result =& $this->_AssociationHandler->create($this->getAssociationId(), $attributes, $replace_existing); 134 } 135 return $result; 136 } 137 138 function &replace(&$NewAssociated, $dont_save = false) 139 { 140 $result = false; 141 if(!empty($this->_AssociationHandler)){ 142 $result =& $this->_AssociationHandler->replace($this->getAssociationId(), $NewAssociated, $dont_save = false); 143 } 144 return $result; 145 } 146 147 function &find() 148 { 149 $result = false; 150 if(!empty($this->_AssociationHandler)){ 151 $result =& $this->_AssociationHandler->findAssociated($this->getAssociationId()); 152 } 153 return $result; 154 } 155 156 function &load() 157 { 158 $result = false; 159 if(!empty($this->_AssociationHandler)){ 160 $result =& $this->_AssociationHandler->loadAssociated($this->getAssociationId()); 161 } 162 return $result; 163 } 164 165 function constructSql() 166 { 167 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->constructSql($this->getAssociationId()) : false; 168 } 169 170 function constructSqlForInclusion() 171 { 172 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->constructSqlForInclusion($this->getAssociationId()) : false; 173 } 174 175 function getAssociatedFinderSqlOptions($options = array()) 176 { 177 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getAssociatedFinderSqlOptions($this->getAssociationId(), $options) : false; 178 } 179 180 function getAssociationOption($option) 181 { 182 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getOption($this->getAssociationId(), $option) : false; 183 } 184 185 function setAssociationOption($option, $value) 186 { 187 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->setOption($this->getAssociationId(), $option, $value) : false; 188 } 189 190 function getAssociationId() 191 { 192 if(empty($this->_associationId)){ 193 trigger_error(Ak::t('You are trying to access a non associated Object property. '. 194 'This error might have been caused by asigning directly an object '. 195 'to the association instead of using the "assign()" method'),E_USER_WARNING); 196 } 197 return $this->_associationId; 198 } 199 200 function getAssociatedIds() 201 { 202 return array_keys($this->_associationIds); 203 } 204 205 function getAssociatedHandlerName($association_id) 206 { 207 return empty($this->_associationIds[$association_id]) ? false : $this->_associationIds[$association_id]; 208 } 209 210 function getAssociatedType() 211 { 212 return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getType() : false; 213 } 214 215 function getAssociationType() 216 { 217 return $this->getAssociatedType(); 218 } 219 220 function getType() 221 { 222 return $this->getAssociatedType(); 223 } 224 225 226 function hasAssociations() 227 { 228 return !empty($this->_associations) && count($this->_associations) > 0; 229 } 230 231 function &findWithAssociations($options) 232 { 233 $result = false; 234 $options['include'] = Ak::toArray($options['include']); 235 $options['order'] = empty($options['order']) ? '' : $this->_addTableAliasesToAssociatedSql('__owner', $options['order']); 236 $options['conditions'] = empty($options['conditions']) ? '' : $this->_addTableAliasesToAssociatedSql('__owner', $options['conditions']); 237 238 $included_associations = array(); 239 $included_association_options = array(); 240 foreach ($options['include'] as $k=>$v){ 241 if(is_numeric($k)){ 242 $included_associations[] = $v; 243 }else { 244 $included_associations[] = $k; 245 $included_association_options[$k] = $v; 246 } 247 } 248 249 $available_associated_options = array('order'=>array(), 'conditions'=>array(), 'joins'=>array(), 'selection'=>array()); 250 251 foreach ($included_associations as $association_id){ 252 $association_options = empty($included_association_options[$association_id]) ? array() : $included_association_options[$association_id]; 253 254 $handler_name = $this->getCollectionHandlerName($association_id); 255 $handler_name = empty($handler_name) ? $association_id : (in_array($handler_name, $included_associations) ? $association_id : $handler_name); 256 $associated_options = $this->$handler_name->getAssociatedFinderSqlOptions($association_options); 257 foreach (array_keys($available_associated_options) as $associated_option){ 258 if(!empty($associated_options[$associated_option])){ 259 $available_associated_options[$associated_option][] = $associated_options[$associated_option]; 260 } 261 } 262 } 263 264 foreach ($available_associated_options as $option=>$values){ 265 if(!empty($values)){ 266 $separator = $option == 'joins' ? ' ' : (in_array($option, array('selection','order')) ? ', ': ' AND '); 267 $values = array_map('trim', $values); 268 $options[$option] = empty($options[$option]) ? 269 join($separator, $values) : 270 trim($options[$option]).$separator.join($separator, $values); 271 } 272 } 273 274 $sql = trim($this->constructFinderSqlWithAssociations($options)); 275 276 if(!empty($options['bind']) && is_array($options['bind']) && strstr($sql,'?')){ 277 $sql = array_merge(array($sql),$options['bind']); 278 } 279 $result =& $this->_findBySqlWithAssociations($sql, $options['include'], empty($options['virtual_limit']) ? false : $options['virtual_limit']); 280 281 return $result; 282 } 283 284 285 function getCollectionHandlerName($association_id) 286 { 287 if(isset($this->$association_id) && is_object($this->$association_id) && method_exists($this->$association_id,'getAssociatedFinderSqlOptions')){ 288 return false; 289 } 290 $collection_handler_name = AkInflector::singularize($association_id); 291 if(isset($this->$collection_handler_name) && 292 is_object($this->$collection_handler_name) && 293 in_array($this->$collection_handler_name->getType(),array('hasMany','hasAndBelongsToMany'))){ 294 return $collection_handler_name; 295 }else{ 296 return false; 297 } 298 } 299 300 301 /** 302 * Used for generating custom selections for habtm, has_many and has_one queries 303 */ 304 function constructFinderSqlWithAssociations($options, $include_owner_as_selection = true) 305 { 306 $sql = 'SELECT '; 307 $selection = ''; 308 if($include_owner_as_selection){ 309 foreach (array_keys($this->getColumns()) as $column_name){ 310 $selection .= '__owner.'.$column_name.' AS __owner_'.$column_name.', '; 311 } 312 $selection .= (isset($options['selection']) ? $options['selection'].' ' : ''); 313 $selection = trim($selection,', ').' '; // never used by the unit tests 314 }else{ 315 // used only by HasOne::findAssociated 316 $selection .= $options['selection'].'.* '; 317 } 318 $sql .= $selection; 319 $sql .= 'FROM '.($include_owner_as_selection ? $this->getTableName().' AS __owner ' : $options['selection'].' '); 320 $sql .= (!empty($options['joins']) ? $options['joins'].' ' : ''); 321 322 empty($options['conditions']) ? null : $this->addConditions($sql, $options['conditions'], '__owner'); 323 324 // Create an alias for order 325 if(empty($options['order']) && !empty($options['sort'])){ 326 $options['order'] = $options['sort']; 327 } 328 $sql .= !empty($options['order']) ? ' ORDER BY '.$options['order'] : ''; 329 330 $this->_db->addLimitAndOffset($sql,$options); 331 return $sql; 332 } 333 334 335 /** 336 * @todo Refactor in order to increase performance of associated inclussions 337 */ 338 function &_findBySqlWithAssociations($sql, $included_associations = array(), $virtual_limit = false) 339 { 340 $objects = array(); 341 $results = $this->_db->execute ($sql,'find with associations'); 342 if (!$results){ 343 return $objects; 344 } 345 $result =& $this->_generateObjectGraphFromResultSet($results,$included_associations,$virtual_limit); 346 return $result; 347 } 348 349 /** 350 * Pass hand-made sql directly to _db->execute and generate the OG with this method. 351 * 352 * @param ADOResultSet $results a result set from Db->execute 353 * @param array $included_associations just like in ->find(); $options['include']; but in fact unused 354 * @param mixed $virtual_limit int or false; unsure if this works 355 * @return array ObjectGraph as an array 356 */ 357 function &_generateObjectGraphFromResultSet($results,$included_associations = array(), $virtual_limit = false) 358 { 359 $objects = array(); 360 361 $i = 0; 362 $associated_ids = $this->getAssociatedIds(); 363 $number_of_associates = count($associated_ids); 364 $_included_results = array(); // Used only in conjuntion with virtual limits for doing find('first',...include'=>... 365 $object_associates_details = array(); 366 $ids = array(); 367 while ($record = $results->FetchRow()) { 368 $this_item_attributes = array(); 369 $associated_items = array(); 370 foreach ($record as $column=>$value){ 371 if(!is_numeric($column)){ 372 if(substr($column,0,8) == '__owner_'){ 373 $attribute_name = substr($column,8); 374 $this_item_attributes[$attribute_name] = $value; 375 }elseif(preg_match('/^_('.join('|',$associated_ids).')_(.+)/',$column, $match)){ 376 $associated_items[$match[1]][$match[2]] = $value; 377 } 378 } 379 } 380 381 // We need to keep a pointer to unique parent elements in order to add associates to the first loaded item 382 $e = null; 383 $object_id = $this_item_attributes[$this->getPrimaryKey()]; 384 385 if(!empty($virtual_limit)){ 386 $_included_results[$object_id] = $object_id; 387 if(count($_included_results) > $virtual_limit * $number_of_associates){ 388 continue; 389 } 390 } 391 392 if(!isset($ids[$object_id])){ 393 $ids[$object_id] = $i; 394 $attributes_for_instantation = $this->getOnlyAvailableAttributes($this_item_attributes); 395 $attributes_for_instantation['load_associations'] = true; 396 $objects[$i] =& $this->instantiate($attributes_for_instantation, false); 397 }else{ 398 $e = $i; 399 $i = $ids[$object_id]; 400 } 401 402 foreach ($associated_items as $association_id=>$attributes){ 403 if(count(array_diff($attributes, array(''))) > 0){ 404 $object_associates_details[$i][$association_id][md5(serialize($attributes))] = $attributes; 405 } 406 } 407 408 $i = !is_null($e) ? $e : $i+1; 409 } 410 411 if(!empty($object_associates_details)){ 412 foreach ($object_associates_details as $i=>$object_associate_details){ 413 foreach ($object_associate_details as $association_id => $associated_attributes){ 414 foreach ($associated_attributes as $attributes){ 415 if(count(array_diff($attributes, array(''))) > 0){ 416 if(!method_exists($objects[$i]->$association_id, 'build')){ 417 $handler_name = $this->getAssociatedHandlerName($association_id); 418 $objects[$i]->$handler_name->build($attributes, false); 419 }else{ 420 $objects[$i]->$association_id->build($attributes, false); 421 $objects[$i]->$association_id->_newRecord = false; 422 } 423 } 424 } 425 } 426 } 427 } 428 429 $result =& $objects; 430 return $result; 431 } 432 433 434 function _addTableAliasesToAssociatedSql($table_alias, $sql) 435 { 436 return preg_replace($this->getColumnsWithRegexBoundaries(),'\1'.$table_alias.'.\2',' '.$sql.' '); 437 } 438 439 } 440 441 442 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Oct 27 12:43:49 2008 | Cross-referenced by PHPXref 0.6 |