| [ 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 Associations 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 require_once (AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkAssociation.php'); 19 20 /** 21 * Adds the following methods for retrieval and query of collections of associated objects. 22 * collection is replaced with the singular form of current association, 23 * so var $has_many = 'clients' would hold an array of objects on $this->clients 24 * and a collection handling interface instance on $this->client (singular form) 25 * 26 * * collection->load($force_reload = false) - returns an array of all the associated objects. An empty array is returned if none are found. 27 * * collection->add($object, ?) - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. 28 * (collection->push and $collection->concat are aliases to this method). 29 * * collection->delete($object, ?) - removes one or more objects from the collection by setting their foreign keys to NULL. This will also destroy the objects if they?re declared as belongs_to and dependent on this model. 30 * * collection->set($objects) - replaces the collections content by deleting and adding objects as appropriate. 31 * * collection->setByIds($ids) - replace the collection by the objects identified by the primary keys in ids 32 * * collection->clear() - removes every object from the collection. This destroys the associated objects if they are 'dependent', deletes them directly from the database if they are 'dependent' => 'delete_all', and sets their foreign keys to NULL otherwise. 33 * * collection->isEmpty() - returns true if there are no associated objects. 34 * * collection->getSize() - returns the number of associated objects. 35 * * collection->find() - finds an associated object according to the same rules as ActiveRecord->find. 36 * * collection->count() - returns the number of elements associated. (collection->size() is an alias to this method) 37 * * collection->build($attributes = array()) - returns a new object of the collection 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 associated object already exists, not if it?s null 38 * * collection->create($attributes = array()) - returns a new object of the collection 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). *Note:* This only works if an associated object already exists, not if it?s null 39 * 40 * Example: A Firm class declares has_many clients, which will add: 41 * 42 * * Firm->client->load() (similar to $Clients->find('all', array('conditions' => 'firm_id = '.$id)) ) 43 * * Firm->client->add() 44 * * Firm->client->delete() 45 * * Firm->client->assign() 46 * * Firm->client->assignByIds() 47 * * Firm->client->clear() 48 * * Firm->client->isEmpty() (similar to count($Firm->clients) == 0) 49 * * Firm->client->getSize() (similar to Client.count "firm_id = #{id}") 50 * * Firm->client->find() (similar to $Client->find($id, array('conditions' => 'firm_id = '.$id)) ) 51 * * Firm->client->build() (similar to new Client(array('firm_id' => $id)) ) 52 * * Firm->client->create() (similar to $c = new Client(array('firm_id' => $id)); $c->save(); return $c ) 53 * 54 * The declaration can also include an options array to specialize the behavior of the association. 55 * 56 * Options are: 57 * 58 * * 'class_name' - specify the class name of the association. Use it only if that name can't be inferred from the association name. So "$has_many = 'products'" will by default be linked to the Product class, but if the real class name is SpecialProduct, you?ll have to specify it with this option. 59 * * 'conditions' - specify the conditions that the associated objects must meet in order to be included as a "WHERE" sql fragment, such as "price > 5 AND name LIKE ?B%?". 60 * * 'order' - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC" 61 * * 'group' - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment, such as "category" 62 * * 'foreign_key' - specify the foreign key used for the association. By default this is guessed to be the name of this class in lower-case and "_id" suffixed. So a Person class that makes a has_many association will use "person_id" as the default foreign_key. 63 * * 'dependent' - if set to 'destroy' all the associated objects are destroyed alongside this object by calling their destroy method. If set to 'delete_all' all associated objects are deleted without calling their destroy method. If set to 'nullify' all associated objects? foreign keys are set to NULL without calling their save callbacks. 64 * * 'finder_sql' - specify a complete SQL statement to fetch the association. This is a good way to go for complex associations that depend on multiple tables. Note: When this option is used, findInCollection is not added. 65 * * 'counter_sql' - specify a complete SQL statement to fetch the size of the association. If +'finder_sql'+ is specified but +'counter_sql'+, +'counter_sql'+ will be generated by replacing SELECT ? FROM with SELECT COUNT(*) FROM. 66 * * 'include' - specify second-order associations that should be eager loaded when the collection is loaded. 67 * * 'group' An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. 68 * * 'limit' An integer determining the limit on the number of rows that should be returned. 69 * * 'offset' An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. 70 * * 'select' By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not include the joined columns. 71 * 72 * Option examples: 73 * 74 * $has_many = array( 75 * 'comments' => array('order' => 'posted_on', 'include' => 'author', 'dependent' => 'nullify'), 76 * 'people' => array('conditions' => 'deleted = 0', 'order' => 'name'), 77 * 'tracks' => array('order' => 'position', 'dependent' => 'destroy'), 78 * 'members' => array('class_name' => 'Person', 'conditions' => 'role = "merber"')); 79 */ 80 class AkHasMany extends AkAssociation 81 { 82 var $associated_ids = array(); 83 var $association_id; 84 85 function &addAssociated($association_id, $options = array()) 86 { 87 88 $default_options = array( 89 'class_name' => empty($options['class_name']) ? AkInflector::classify($association_id) : $options['class_name'], 90 'conditions' => false, 91 'order' => false, 92 'include_conditions_when_included' => true, 93 'include_order_when_included' => true, 94 'group' => false, 95 'foreign_key' => false, 96 'dependent' => 'nullify', 97 'finder_sql' => false, 98 'counter_sql' => false, 99 'include' => false, 100 'instantiate' => false, 101 'group' => false, 102 'limit' => false, 103 'offset' => false, 104 'handler_name' => strtolower(AkInflector::underscore(AkInflector::singularize($association_id))), 105 'select' => false 106 ); 107 108 $options = array_merge($default_options, $options); 109 110 $options['foreign_key'] = empty($options['foreign_key']) ? AkInflector::underscore($this->Owner->getModelName()).'_id' : $options['foreign_key']; 111 112 $Collection =& $this->_setCollectionHandler($association_id, $options['handler_name']); 113 $Collection->setOptions($association_id, $options); 114 115 116 $this->addModel($association_id, $Collection); 117 118 if($options['instantiate']){ 119 $associated =& $Collection->load(); 120 } 121 122 $this->setAssociatedId($association_id, $options['handler_name']); 123 $Collection->association_id = $association_id; 124 125 return $Collection; 126 } 127 128 function getType() 129 { 130 return 'hasMany'; 131 } 132 133 function &_setCollectionHandler($association_id, $handler_name) 134 { 135 if(isset($this->Owner->$association_id)){ 136 if(!is_array($this->Owner->$association_id)){ 137 trigger_error(Ak::t('%model_name::%association_id is not a collection array on current %association_id hasMany association',array('%model_name'=>$this->Owner->getModelName(), '%association_id'=>$association_id)), E_USER_NOTICE); 138 } 139 $associated =& $this->Owner->$association_id; 140 }else{ 141 $associated = array(); 142 $this->Owner->$association_id =& $associated; 143 } 144 145 if(isset($this->Owner->$handler_name)){ 146 trigger_error(Ak::t('Could not load %association_id on %model_name because "%model_name->%handler_name" attribute '. 147 'is already defined and can\'t be used as an association placeholder', 148 array('%model_name'=>$this->Owner->getModelName(),'%association_id'=>$association_id, '%handler_name'=>$handler_name)), 149 E_USER_ERROR); 150 return false; 151 }else{ 152 $this->Owner->$handler_name =& new AkHasMany($this->Owner); 153 } 154 return $this->Owner->$handler_name; 155 } 156 157 158 function &load($force_reload = false) 159 { 160 $options = $this->getOptions($this->association_id); 161 if($force_reload || empty($this->Owner->{$options['handler_name']}->_loaded)){ 162 if(!$this->Owner->isNewRecord()){ 163 $this->constructSql(false); 164 $options = $this->getOptions($this->association_id); 165 $Associated =& $this->getAssociatedModelInstance(); 166 $finder_options = array('conditions'=>$options['finder_sql']); 167 if(!empty($options['order'])){ 168 $finder_options['order'] = $options['order']; 169 } 170 if(!empty($options['include'])){ 171 $finder_options['include'] = $options['include']; 172 } 173 if($FoundAssociates = $Associated->find('all',$finder_options)){ 174 array_map(array(&$this,'_setAssociatedMemberId'),$FoundAssociates); 175 $this->Owner->{$this->association_id} =& $FoundAssociates; 176 } 177 } 178 if(empty($this->Owner->{$this->association_id})){ 179 $this->Owner->{$this->association_id} = array(); 180 } 181 182 $this->Owner->{$options['handler_name']}->_loaded = true; 183 } 184 return $this->Owner->{$this->association_id}; 185 } 186 187 188 /** 189 * add($object), add(array($object, $object2)) - adds one or more objects to the collection by setting 190 * their foreign keys to the collection?s primary key. Items are saved automatically when parent has been saved. 191 */ 192 function add(&$Associated) 193 { 194 if(is_array($Associated)){ 195 $succes = true; 196 $succes = $this->Owner->notifyObservers('beforeAdd') ? $succes : false; 197 $options = $this->getOptions($this->association_id); 198 foreach (array_keys($Associated) as $k){ 199 if($succes && !empty($options['before_add']) && method_exists($this->Owner, $options['before_add']) && $this->Owner->{$options['before_add']}($Associated[$k]) === false ){ 200 $succes = false; 201 } 202 if($succes && !$this->_hasAssociatedMember($Associated[$k])){ 203 $this->Owner->{$this->association_id}[] =& $Associated[$k]; 204 $this->_setAssociatedMemberId($Associated[$k]); 205 if($this->_relateAssociatedWithOwner($Associated[$k])){ 206 $succes = $Associated[$k]->save() ? $succes : false; 207 if($succes && !empty($options['after_add']) && method_exists($this->Owner, $options['after_add']) && $this->Owner->{$options['after_add']}($Associated[$k]) === false ){ 208 $succes = false; 209 } 210 } 211 } 212 } 213 $succes = $this->Owner->notifyObservers('afterAdd') ? $succes : false; 214 return $succes; 215 }else{ 216 $associates = array(); 217 $associates[] =& $Associated; 218 return $this->add($associates); 219 } 220 } 221 222 function push(&$record) 223 { 224 return $this->add($record); 225 } 226 227 function concat(&$record) 228 { 229 return $this->add($record); 230 } 231 232 /** 233 * Remove all records from this association 234 */ 235 function deleteAll($Skip = null) 236 { 237 $this->load(); 238 return $this->delete($this->Owner->{$this->association_id}, $Skip); 239 } 240 241 function reset() 242 { 243 $options = $this->getOptions($this->association_id); 244 $this->Owner->{$options['handler_name']}->_loaded = false; 245 } 246 247 function set(&$objects) 248 { 249 $this->deleteAll($objects); 250 $this->add($objects); 251 } 252 253 function setIds() 254 { 255 $ids = func_get_args(); 256 $ids = is_array($ids[0]) ? $ids[0] : $ids; 257 258 $AssociatedModel =& $this->getAssociatedModelInstance(); 259 if(!empty($ids)){ 260 $NewAssociates =& $AssociatedModel->find($ids); 261 $this->set($NewAssociates); 262 } 263 } 264 265 function setByIds() 266 { 267 $ids = func_get_args(); 268 call_user_func_array(array($this,'setIds'), $ids); 269 } 270 271 function addId($id) 272 { 273 $AssociatedModel =& $this->getAssociatedModelInstance(); 274 if($NewAssociated = $AssociatedModel->find($id)){ 275 return $this->add($NewAssociated); 276 } 277 return false; 278 } 279 280 281 function delete(&$Associated, $Skip = null) 282 { 283 $success = true; 284 if(!is_array($Associated)){ 285 $associated_elements = array(); 286 $associated_elements[] =& $Associated; 287 return $this->delete($associated_elements, $Skip); 288 }else{ 289 $options = $this->getOptions($this->association_id); 290 291 $ids_to_skip = array(); 292 $Skip = empty($Skip) ? null : (is_array($Skip) ? $Skip : array($Skip)); 293 if(!empty($Skip)){ 294 foreach (array_keys($Skip) as $k){ 295 $ids_to_skip[] = $Skip[$k]->getId(); 296 } 297 } 298 299 $ids_to_nullify = array(); 300 $ids_to_delete = array(); 301 $items_to_remove_from_collection = array(); 302 $AssociatedModel =& $this->getAssociatedModelInstance(); 303 304 $owner_type = $this->_findOwnerTypeForAssociation($AssociatedModel, $this->Owner); 305 306 foreach (array_keys($Associated) as $k){ 307 $items_to_remove_from_collection[] = $Associated[$k]->getId(); 308 if(!in_array($Associated[$k]->getId() , $ids_to_skip)){ 309 switch ($options['dependent']) { 310 case 'destroy': 311 $success = $Associated[$k]->destroy() ? $success : false; 312 break; 313 case 'delete_all': 314 $ids_to_delete[] = $Associated[$k]->getId(); 315 break; 316 case 'nullify': 317 $id_to_nullify = $Associated[$k]->quotedId(); 318 if(!empty($id_to_nullify)){ 319 $ids_to_nullify[] = $id_to_nullify; 320 } 321 default: 322 break; 323 } 324 } 325 } 326 327 $ids_to_nullify = empty($ids_to_nullify) ? false : array_diff($ids_to_nullify,array('')); 328 if(!empty($ids_to_nullify)){ 329 $success = $AssociatedModel->updateAll( 330 ' '.$options['foreign_key'].' = NULL ', 331 ' '.$options['foreign_key'].' = '.$this->Owner->quotedId().' AND '.$AssociatedModel->getPrimaryKey().' IN ('.join(', ',$ids_to_nullify).')' 332 ) ? $success : false; 333 }elseif(!empty($ids_to_delete)){ 334 $success = $AssociatedModel->delete($ids_to_delete) ? $success : false; 335 } 336 337 $this->removeFromCollection($items_to_remove_from_collection); 338 } 339 340 return $success; 341 } 342 343 344 345 /** 346 * Remove records from the collection. Use delete() in order to trigger database dependencies 347 */ 348 function removeFromCollection(&$records) 349 { 350 if(!is_array($records)){ 351 $records_array = array(); 352 $records_array[] =& $records; 353 $this->delete($records_array); 354 }else{ 355 $this->Owner->notifyObservers('beforeRemove'); 356 $options = $this->getOptions($this->association_id); 357 foreach (array_keys($records) as $k){ 358 359 if(!empty($options['before_remove']) && method_exists($this->Owner, $options['before_remove']) && $this->Owner->{$options['before_remove']}($records[$k]) === false ){ 360 continue; 361 } 362 363 if(isset($records[$k]->__activeRecordObject)){ 364 $record_id = $records[$k]->getId(); 365 }else{ 366 $record_id = $records[$k]; 367 } 368 369 foreach (array_keys($this->Owner->{$this->association_id}) as $kk){ 370 if( 371 ( 372 !empty($this->Owner->{$this->association_id}[$kk]->__hasManyMemberId) && 373 !empty($records[$k]->__hasManyMemberId) && 374 $records[$k]->__hasManyMemberId == $this->Owner->{$this->association_id}[$kk]->__hasManyMemberId 375 ) || ( 376 !empty($this->Owner->{$this->association_id}[$kk]->__activeRecordObject) && 377 $record_id == $this->Owner->{$this->association_id}[$kk]->getId() 378 ) 379 ){ 380 unset($this->Owner->{$this->association_id}[$kk]); 381 } 382 } 383 384 $this->_unsetAssociatedMemberId($records[$k]); 385 386 if(!empty($options['after_remove']) && method_exists($this->Owner, $options['after_remove'])){ 387 $this->Owner->{$options['after_remove']}($records[$k]); 388 } 389 390 } 391 $this->Owner->notifyObservers('afterRemove'); 392 } 393 } 394 395 396 397 398 function _setAssociatedMemberId(&$Member) 399 { 400 if(empty($Member->__hasManyMemberId)) { 401 $Member->__hasManyMemberId = Ak::randomString(); 402 } 403 $object_id = method_exists($Member,'getId') ? $Member->getId() : null; 404 if(!empty($object_id)){ 405 $this->associated_ids[$object_id] = $Member->__hasManyMemberId; 406 } 407 } 408 409 function _unsetAssociatedMemberId(&$Member) 410 { 411 $id = $this->_getAssociatedMemberId($Member); 412 unset($this->associated_ids[$id]); 413 unset($Member->__hasManyMemberId); 414 } 415 416 function _getAssociatedMemberId(&$Member) 417 { 418 if(!empty($Member->__hasManyMemberId)) { 419 return array_search($Member->__hasManyMemberId, $this->associated_ids); 420 } 421 return false; 422 } 423 424 function _hasAssociatedMember(&$Member) 425 { 426 return !empty($Member->__hasManyMemberId); 427 } 428 429 430 function _relateAssociatedWithOwner(&$Associated) 431 { 432 if(!$this->Owner->isNewRecord()){ 433 if(method_exists($Associated, 'getModelName')){ 434 $foreign_key = $this->getOption($this->association_id, 'foreign_key'); 435 if($this->getOption($this->association_id, 'class_name') != $Associated->getModelName() || $foreign_key == $Associated->get($foreign_key)){ 436 return false; 437 } 438 $Associated->set($foreign_key, $this->Owner->getId()); 439 return true; 440 } 441 } 442 return false; 443 } 444 445 function &_build($association_id, &$AssociatedObject, $reference_associated = true) 446 { 447 if($reference_associated){ 448 $this->Owner->$association_id =& $AssociatedObject; 449 }else{ 450 $this->Owner->$association_id = $AssociatedObject; 451 } 452 $this->Owner->$association_id->_AssociationHandler =& $this; 453 $this->Owner->$association_id->_associatedAs = $this->getType(); 454 $this->Owner->$association_id->_associationId = $association_id; 455 $this->Owner->_associations[$association_id] =& $this->Owner->$association_id; 456 return $this->Owner->$association_id; 457 } 458 459 460 461 462 function constructSql($set_owner_table_has_included = true) 463 { 464 $options = $this->getOptions($this->association_id); 465 $Associated =& $this->getAssociatedModelInstance(); 466 $owner_id = $this->Owner->quotedId(); 467 $table_name = (!empty($options['include']) || $set_owner_table_has_included) ? '__owner' : $Associated->getTableName(); 468 469 if(empty($options['finder_sql'])){ 470 $options['finder_sql'] = ' '.$table_name.'.'.$options['foreign_key'].' = '.(empty($owner_id) ? 'null' : $owner_id).' '; 471 $options['finder_sql'] .= !empty($options['conditions']) ? ' AND '.$options['conditions'].' ' : ''; 472 } 473 if(empty($options['counter_sql']) && !empty($options['finder_sql'])){ 474 $options['counter_sql'] = $options['finder_sql']; 475 }elseif(empty($options['counter_sql'])){ 476 $options['counter_sql'] = ' '.$table_name.'.'.$options['foreign_key'].' = '.(empty($owner_id) ? 'null' : $owner_id).' '; 477 $options['counter_sql'] .= !empty($options['conditions']) ? ' AND '.$options['conditions'].' ' : ''; 478 } 479 480 if(!empty($options['counter_sql']) && strtoupper(substr($options['counter_sql'],0,6)) != 'SELECT'){ 481 $options['counter_sql'] = 'SELECT COUNT(*) FROM '.$table_name.' WHERE '.$options['counter_sql']; 482 } 483 484 $this->setOptions($this->association_id, $options); 485 } 486 487 488 489 function count($force_count = false) 490 { 491 $count = 0; 492 $options = $this->getOptions($this->association_id); 493 if($force_count || (empty($this->Owner->{$options['handler_name']}->_loaded) && !$this->Owner->isNewRecord())){ 494 $this->constructSql(false); 495 $options = $this->getOptions($this->association_id); 496 $Associated =& $this->getAssociatedModelInstance(); 497 498 if($this->_hasCachedCounter()){ 499 $count = $Associated->getAttribute($this->_getCachedCounterAttributeName()); 500 }elseif(!empty($options['counter_sql'])){ 501 $count = $Associated->countBySql($options['counter_sql']); 502 }else{ 503 $count = (strtoupper(substr($options['finder_sql'],0,6)) != 'SELECT') ? 504 $Associated->count($options['foreign_key'].'='.$this->Owner->quotedId()) : 505 $Associated->countBySql($options['finder_sql']); 506 } 507 }else{ 508 $count = count($this->Owner->{$this->association_id}); 509 } 510 511 if($count == 0){ 512 $this->Owner->{$this->association_id} = array(); 513 $this->Owner->{$options['handler_name']}->_loaded = true; 514 } 515 516 return $count; 517 } 518 519 function size() 520 { 521 return $this->count(); 522 } 523 524 525 function &build($attributes = array(), $set_as_new_record = true) 526 { 527 $options = $this->getOptions($this->association_id); 528 Ak::import($options['class_name']); 529 $record =& new $options['class_name']($attributes); 530 $record->_newRecord = $set_as_new_record; 531 $this->Owner->{$this->association_id}[] =& $record; 532 $this->_setAssociatedMemberId($record); 533 $this->_relateAssociatedWithOwner($record); 534 return $record; 535 } 536 537 function &create($attributes = array()) 538 { 539 $record =& $this->build($attributes); 540 if(!$this->Owner->isNewRecord()){ 541 $record->save(); 542 } 543 return $record; 544 } 545 546 547 function getAssociatedFinderSqlOptions($association_id, $options = array()) 548 { 549 $options = $this->getOptions($this->association_id); 550 $Associated =& $this->getAssociatedModelInstance(); 551 $table_name = $Associated->getTableName(); 552 $owner_id = $this->Owner->quotedId(); 553 554 $finder_options = array(); 555 556 foreach ($options as $option=>$value) { 557 if(!empty($value)){ 558 $finder_options[$option] = trim($Associated->_addTableAliasesToAssociatedSql('_'.$this->association_id, $value)); 559 } 560 } 561 562 $finder_options['joins'] = $this->constructSqlForInclusion(); 563 $finder_options['selection'] = ''; 564 565 foreach (array_keys($Associated->getColumns()) as $column_name){ 566 $finder_options['selection'] .= '_'.$this->association_id.'.'.$column_name.' AS _'.$this->association_id.'_'.$column_name.', '; 567 } 568 569 $finder_options['selection'] = trim($finder_options['selection'], ', '); 570 571 $finder_options['conditions'] = empty($options['conditions']) ? '' : 572 573 $Associated->_addTableAliasesToAssociatedSql('_'.$this->association_id, $options['conditions']).' '; 574 575 return $finder_options; 576 } 577 578 function constructSqlForInclusion() 579 { 580 $Associated =& $this->getAssociatedModelInstance(); 581 $options = $this->getOptions($this->association_id); 582 return ' LEFT OUTER JOIN '. 583 $Associated->getTableName().' AS _'.$this->association_id. 584 ' ON '. 585 '__owner.'.$this->Owner->getPrimaryKey(). 586 ' = '. 587 '_'.$this->association_id.'.'.$options['foreign_key'].' '; 588 } 589 590 591 function _hasCachedCounter() 592 { 593 $Associated =& $this->getAssociatedModelInstance(); 594 return $Associated->isAttributePresent($this->_getCachedCounterAttributeName()); 595 } 596 597 function _getCachedCounterAttributeName() 598 { 599 return $this->association_id.'_count'; 600 } 601 602 603 function &getAssociatedModelInstance() 604 { 605 static $ModelInstances; 606 $class_name = $this->getOption($this->association_id, 'class_name'); 607 if(empty($ModelInstances[$class_name])){ 608 Ak::import($class_name); 609 $ModelInstances[$class_name] =& new $class_name(); 610 } 611 return $ModelInstances[$class_name]; 612 } 613 614 615 function &find() 616 { 617 $result = false; 618 if(!$this->Owner->isNewRecord()){ 619 620 $args = func_get_args(); 621 $num_args = func_num_args(); 622 623 if(!empty($args[$num_args-1]) && is_array($args[$num_args-1])){ 624 $options_in_args = true; 625 $options = $args[$num_args-1]; 626 }else{ 627 $options_in_args = false; 628 $options = array(); 629 } 630 631 $this->constructSql(!empty($options['include'])); 632 $has_many_options = $this->getOptions($this->association_id); 633 $Associated =& $this->getAssociatedModelInstance(); 634 if (empty($options['conditions'])) { 635 $options['conditions'] = @$has_many_options['finder_sql']; 636 } elseif(!empty($has_many_options['finder_sql']) && is_array($options['conditions']) && !strstr($options['conditions'][0], $has_many_options['finder_sql'])) { 637 $options['conditions'][0] .= ' AND '. $has_many_options['finder_sql']; 638 } elseif (!empty($has_many_options['finder_sql']) && !strstr($options['conditions'], $has_many_options['finder_sql'])) { 639 $options['conditions'] .= ' AND '. $has_many_options['finder_sql']; 640 } 641 642 $options['order'] = empty($options['order']) ? @$has_many_options['order'] : $options['order']; 643 $options['include'] = empty($options['include']) ? @$has_many_options['include'] : $options['include']; 644 645 if($options_in_args){ 646 $args[$num_args-1] = $options; 647 }else{ 648 $args = empty($args) ? array('all') : $args; 649 array_push($args, $options); 650 } 651 652 $result =& Ak::call_user_func_array(array(&$Associated,'find'), $args); 653 } 654 655 return $result; 656 } 657 658 659 function isEmpty() 660 { 661 return $this->count() === 0; 662 } 663 664 function getSize() 665 { 666 return $this->count(); 667 } 668 669 function clear() 670 { 671 return $this->deleteAll(); 672 } 673 674 /** 675 * Triggers 676 */ 677 function afterCreate(&$object) 678 { 679 return $this->_afterCallback($object); 680 } 681 682 function afterUpdate(&$object) 683 { 684 return $this->_afterCallback($object); 685 } 686 687 688 function beforeDestroy(&$object) 689 { 690 $success = true; 691 692 foreach ((array)$object->_associationIds as $k => $v){ 693 if(isset($object->$k) && is_array($object->$k) && isset($object->$v) && method_exists($object->$v, 'getType') && $object->$v->getType() == 'hasMany'){ 694 695 $ids_to_delete = array(); 696 $ids_to_nullify = array(); 697 $items_to_remove_from_collection = array(); 698 699 $object->$v->load(); 700 foreach(array_keys($object->$k) as $key){ 701 702 $items_to_remove_from_collection[] =& $object->{$k}[$key]; 703 704 switch ($object->$v->options[$k]['dependent']) { 705 706 case 'destroy': 707 $success = $object->{$k}[$key]->destroy() ? $success : false; 708 break; 709 710 case 'delete_all': 711 $ids_to_delete[] = $object->{$k}[$key]->getId(); 712 break; 713 714 case 'nullify': 715 $id_to_nullify = $object->{$k}[$key]->quotedId(); 716 if(!empty($id_to_nullify)){ 717 $ids_to_nullify[] = $id_to_nullify; 718 } 719 break; 720 721 default: 722 break; 723 } 724 } 725 726 $ids_to_nullify = empty($ids_to_nullify) ? false : array_diff($ids_to_nullify,array('')); 727 if(!empty($ids_to_nullify)){ 728 $success = $object->{$k}[$key]->updateAll( 729 ' '.$object->$v->options[$k]['foreign_key'].' = NULL ', 730 ' '.$object->$v->options[$k]['foreign_key'].' = '.$object->quotedId().' AND '.$object->{$k}[$key]->getPrimaryKey().' IN ('.join(', ', $ids_to_nullify).')' 731 ) ? $success : false; 732 }elseif(!empty($ids_to_delete)){ 733 $success = $object->{$k}[$key]->delete($ids_to_delete) ? $success : false; 734 } 735 $object->$v->removeFromCollection($items_to_remove_from_collection); 736 } 737 } 738 739 return $success; 740 } 741 742 function _afterCallback(&$object) 743 { 744 745 $success = true; 746 747 $object_id = $object->getId(); 748 foreach (array_keys($object->hasMany->models) as $association_id){ 749 $CollectionHandler =& $object->hasMany->models[$association_id]; 750 $foreign_key = $CollectionHandler->getOption($association_id, 'foreign_key'); 751 $class_name = strtolower($CollectionHandler->getOption($association_id, 'class_name')); 752 if(!empty($object->$association_id) && is_array($object->$association_id)){ 753 foreach (array_keys($object->$association_id) as $k){ 754 if(!empty($object->{$association_id}[$k]) && strtolower(get_class($object->{$association_id}[$k])) == strtolower($class_name)){ 755 $AssociatedItem =& $object->{$association_id}[$k]; 756 $AssociatedItem->set($foreign_key, $object_id); 757 $success = !$AssociatedItem->save() ? false : $success; 758 } 759 } 760 } 761 } 762 return $success; 763 } 764 765 } 766 767 768 ?>
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 |