root/trunk/lib/AkActiveRecord/AkAssociations/AkHasMany.php

Revision 1399, 36.9 kB (checked in by bermi, 1 year ago)

When loading associations using load(), it will always return false is no associates are found.

Converting most Active record files to vanilla PHP5

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