root/trunk/lib/AkActiveRecord/AkAssociatedActiveRecord.php

Revision 1401, 43.8 kB (checked in by arnoschn, 6 months ago)

fixing IN ? bug in findBy queries, fixing bind warning in associated finder

Line 
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     public $__activeRecordObject = false;
43     public $_AssociationHandler;
44     public $_associationId = false;
45     // Holds different association IDs related to this model
46     public $_associationIds = array();
47     public $_associations = array();
48
49     public 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     public function setAssociationHandler(&$AssociationHandler, $association_id)
60     {
61         $this->_AssociationHandler =& $AssociationHandler;
62     }
63
64     public 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
84                 $this->_associations[$association_type] =& $this->$association_type;
85
86             }
87         }
88     }
89
90     /**
91      * Gets an array of associated object of selected association type.
92      */
93     public function &getAssociated($association_type)
94     {
95         $result = array();
96         if(!empty($this->$association_type) && in_array($association_type, array('hasOne','belongsTo','hasMany','hasAndBelongsToMany'))){
97             $result =& $this->$association_type->getModels();
98         }
99         return $result;
100     }
101
102     public function getId()
103     {
104         return false;
105     }
106
107
108     public function &assign(&$Associated)
109     {
110         $result = false;
111         if(is_object($this->_AssociationHandler)){
112             $result =& $this->_AssociationHandler->assign($this->getAssociationId(), $Associated);
113         }
114         return $result;
115     }
116
117     /**
118      * Returns a new object of the associated type that has been instantiated with attributes
119      * and linked to this object through a foreign key but has not yet been saved.
120      */
121     public function &build($attributes = array(), $replace_existing = true)
122     {
123         $result = false;
124         if(!empty($this->_AssociationHandler)){
125             $result =& $this->_AssociationHandler->build($this->getAssociationId(), $attributes, $replace_existing);
126         }
127         return $result;
128     }
129
130
131     public function &create($attributes = array(), $replace_existing = true)
132     {
133         $result = false;
134         if(!empty($this->_AssociationHandler)){
135             $result =& $this->_AssociationHandler->create($this->getAssociationId(), $attributes, $replace_existing);
136         }
137         return $result;
138     }
139
140     public function &replace(&$NewAssociated, $dont_save = false)
141     {
142         $result = false;
143         if(!empty($this->_AssociationHandler)){
144             $result =& $this->_AssociationHandler->replace($this->getAssociationId(), $NewAssociated, $dont_save = false);
145         }
146         return $result;
147     }
148
149     public function &find()
150     {
151         $result = false;
152         if(!empty($this->_AssociationHandler)){
153             $result =& $this->_AssociationHandler->findAssociated($this->getAssociationId());
154         }
155         return $result;
156     }
157
158     public function &load()
159     {
160         $result = false;
161         $association_id = $this->getAssociationId();
162         if(!empty($this->_AssociationHandler) && (empty($this->_loaded))){
163             $result =& $this->_AssociationHandler->loadAssociated($association_id, true);
164         } else if (!empty($this->_AssociationHandler->Owner->$association_id)) {
165             $result = &$this->_AssociationHandler->Owner->$association_id;
166         }
167         return $result;
168     }
169
170     public function constructSql()
171     {
172         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->constructSql($this->getAssociationId()) : false;
173     }
174
175     public function constructSqlForInclusion()
176     {
177         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->constructSqlForInclusion($this->getAssociationId()) : false;
178     }
179     public function constructSqlForInclusionChain($handler_name,$parent_handler_name)
180     {
181         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->constructSqlForInclusionChain($this->getAssociationId(),$handler_name,$parent_handler_name) : false;
182     }
183     public function getAssociatedFinderSqlOptions($options = array())
184     {
185         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getAssociatedFinderSqlOptions($this->getAssociationId(), $options) : false;
186     }
187     public function getAssociatedFinderSqlOptionsForInclusionChain($prefix, $parent_handler_name,$options = array(),$pluralize=false)
188     {
189         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getAssociatedFinderSqlOptionsForInclusionChain($this->getAssociationId(), $prefix, $parent_handler_name, $options,$pluralize) : false;
190     }
191     public function getAssociationOption($option)
192     {
193         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getOption($this->getAssociationId(), $option) : false;
194     }
195
196     public function setAssociationOption($option, $value)
197     {
198         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->setOption($this->getAssociationId(), $option, $value) : false;
199     }
200
201     public function getAssociationId()
202     {
203         if(empty($this->_associationId)){
204             trigger_error(Ak::t('You are trying to access a non associated Object property. '.
205             'This error might have been caused by asigning directly an object '.
206             'to the association instead of using the "assign()" method'),E_USER_WARNING);
207         }
208         return $this->_associationId;
209     }
210
211     public function getAssociatedIds()
212     {
213         return array_keys($this->_associationIds);
214     }
215
216     public function getAssociatedHandlerName($association_id)
217     {
218         return empty($this->_associationIds[$association_id]) ? false : $this->_associationIds[$association_id];
219     }
220
221     public function getAssociatedType()
222     {
223         return !empty($this->_AssociationHandler) ? $this->_AssociationHandler->getType() : false;
224     }
225
226     public function getAssociationType()
227     {
228         return $this->getAssociatedType();
229     }
230
231     public function getType()
232     {
233         return $this->getAssociatedType();
234     }
235
236     public function hasAssociations()
237     {
238         return !empty($this->_associations) && count($this->_associations) > 0;
239     }
240
241     /**
242      * New features:
243      *
244      * $options['returns'] can be default, array, simulated
245      *
246      *     default    - default behaviour, instantiating ActiveRecord Objects
247      *     array      - returning the result as a big array
248      *     simulated  - returning the result as AkActiveRecordMock Objects
249      *
250      * @param array $options
251      * @return array of ActiveRecord Objects
252      */
253     public function &findWithAssociations($options)
254     {
255         $result = false;
256         $options ['include'] = Ak::toArray($options ['include']);
257
258         $load_acts = isset($options['load_acts'])?$options['load_acts']:true;
259
260         $config = array('__owner'=>array('class'=>$this->getType(),'pk'=>$this->getPrimaryKey(),'instance'=>$this));
261
262         $returns = isset($options['returns'])?$options['returns']:'default';
263
264         $simulation_class = isset($options['simulation_class']) && class_exists($options['simulation_class'])?$options['simulation_class']:'AkActiveRecordMock';
265         if (!in_array($returns,array('default','array','simulated'))) {
266             $this->log('option "returns" must be one of default,array,simulated');
267             $returns = 'default';
268         }
269         $included_associations = array ();
270         $included_association_options = array ();
271         foreach ( $options ['include'] as $k => $v ) {
272             if (is_numeric($k)) {
273                 $included_associations [] = $v;
274             } else {
275                 $included_associations [] = $k;
276                 $included_association_options [$k] = $v;
277             }
278         }
279         unset($options['include']);
280         $parent_pk = $this->getPrimaryKey();
281         $available_associated_options = array ('bind'=> array (),'order' => array (), 'conditions' => array (), 'joins' => array (), 'selection' => array () );
282         $replacements = array();
283         foreach ( $included_associations as $association_id ) {
284             $association_options = empty($included_association_options [$association_id]) ? array () : $included_association_options [$association_id];
285
286             $handler_name = $this->getCollectionHandlerName($association_id);
287             $handler_name = empty($handler_name) ? $association_id : (in_array($handler_name, $included_associations) ? $association_id : $handler_name);
288             $type =$this->$handler_name->getType();
289             $multi = false;
290
291             if (in_array($type,array('hasMany','hasAndBelongsToMany'))) {
292                 $multi = true;
293                 $instance = $this->$handler_name->getAssociatedModelInstance();
294                 $class = $instance->getType();
295                 $pk_name = $instance->getPrimaryKey();
296                 $table_name =$instance->getTableName();
297             } else {
298                 $class = $this->$handler_name->getAssociationOption('class_name');
299                 if(!class_exists($class)) {
300                     Ak::import($class);
301                 }
302
303                 if(is_string($class)) {
304                     $instance = new $class;
305                     $pk_name = $instance->getPrimaryKey();
306                     $table_name =$instance->getTableName();
307                 } else {
308
309                     continue;
310                 }
311             }
312             $config['__owner'][$handler_name] = array('class'=>$class,'association_id'=>$association_id,'pk'=>$pk_name,'instance'=>$instance);
313
314             if(isset($association_options['conditions']) && is_array($association_options['conditions'])) {
315                 $true=true;
316                 $_conditions = array_shift($association_options['conditions']);
317                 if(empty($association_options['bind'])) {
318                     $association_options['bind'] = array();
319                 }
320                 $association_options['bind'] = array_merge($association_options['bind'], $association_options['conditions']);
321                 $association_options['conditions'] = $_conditions;
322             }
323
324             $associated_options = $this->$handler_name->getAssociatedFinderSqlOptionsForInclusionChain('owner[@'.$parent_pk.']','__owner',$association_options,$multi);
325
326             $options ['order'] = empty($options ['order']) ? '' : $this->_addTableAliasesToAssociatedSql('__owner', $options ['order']);
327
328             $options ['group'] = empty($options ['group']) ? '' : $this->_addTableAliasesToAssociatedSql('__owner', $options ['group']);
329
330
331
332             $options ['conditions'] = empty($options ['conditions']) ? '' : $this->_addTableAliasesToAssociatedSql('__owner', $options ['conditions']);
333
334
335             foreach(array_keys($associated_options) as $option) {
336                 if(isset($associated_options[$option]) && is_string($associated_options[$option]))$associated_options[$option]=trim($associated_options[$option]);
337                 if(!empty($associated_options[$option])) {
338                     if($option=='bind') {
339                         $available_associated_options[$option] = array_merge((array)$available_associated_options[$option],(array)$associated_options[$option]);
340                     } else {
341                         $available_associated_options[$option][]=$associated_options[$option];
342                     }
343                 }
344             }
345             $replacements['/ ('.$this->getTableName().')\./']=' __owner.';
346             $replacements['/^_('.$association_id.')\./']='__owner__'.$handler_name.'.';
347             $replacements['/ _('.$association_id.')\./'] = ' __owner__'.$handler_name.'.';
348             $replacements['/^_('.$table_name.')\./']='__owner__'.$handler_name.'.';
349             $replacements['/ _('.$table_name.')\./'] = ' __owner__'.$handler_name.'.';
350             $replacements['/^('.$table_name.')\./']='__owner__'.$handler_name.'.';
351             $replacements['/ ('.$table_name.')\./'] = ' __owner__'.$handler_name.'.';
352
353
354             $this->_prepareIncludes('owner[@'.$parent_pk.']',$multi,$this,$available_associated_options,$handler_name,$handler_name,$association_id,$options,$association_options,$replacements, $config['__owner']);
355
356         }
357         //$this->log('Config:'.var_export($config,true));
358         $replace_regex = array_keys($replacements);
359         $replace_value = array_values($replacements);
360         if(isset($options['order'])) $options['order'] = preg_replace($replace_regex,$replace_value,$options['order']);
361         if(isset($options['conditions'])) $options['conditions'] = preg_replace($replace_regex,$replace_value,$options['conditions']);
362         if(isset($options['group']))$options['group'] = preg_replace($replace_regex,$replace_value,$options['group']);
363
364         foreach ( $available_associated_options as $option => $values ) {
365             if($option == 'order' || $option=='conditions' || $option == 'group') {
366                 foreach($values as $idx=>$value) {
367                     $available_associated_options[$option][$idx] = preg_replace($replace_regex,$replace_value,$value);
368                 }
369             }
370
371             if (! empty($values) && $option!='include') {
372                 if(!empty($true)) {
373                     //echo "<pre>";
374                     //var_dump($option);
375                     //var_dump($values);
376                     //var_dump($associated_options);
377                     //die;
378                 }
379                 $separator = $option == 'joins' ? ' ' : (in_array($option, array ('selection', 'order' )) ? ', ' : ' AND ');
380                 $values = array_map('trim', $values);
381
382                 if ($option == 'joins' && ! empty($options [$option])) {
383                     $newJoinParts = array ();
384                     foreach ( $values as $part ) {
385
386                         if (! stristr($options [$option], $part) && !empty($part)) {
387                             $newJoinParts [] = $part;
388                         }
389                     }
390                     $values = $newJoinParts;
391                 }
392                 if($option!='include' && $option!='bind') {
393                     $options [$option] = empty($options [$option]) ? join($separator, $values) : trim($options [$option]) . $separator . join(
394                     $separator, $values);
395                 } else if ($option=='bind') {
396                     if(!isset($options [$option])) {
397                         $options [$option]=array();
398                     }
399                     $options [$option] = array_merge($options [$option],$values);
400                 }
401
402             }
403         }
404
405         $sql = trim($this->constructFinderSqlWithAssociations($options));
406
407         $sql = preg_replace('/,\s*,/',' , ',$sql);
408
409         if (isset($options['wrap'])) {
410             $addLimit='';
411             if(preg_match('/LIMIT ([\d]+){1}(,){0,1}(\s*)([\d]+){0,1}/i',$sql,$matches) && strstr($options['wrap'],'{limit}')) {
412                 $sql = str_replace($matches[0],'',$sql);
413                 $addLimit = $matches[0];
414             }
415             $sql = str_replace('{query}',$sql,$options['wrap']);
416             //if(!empty($addLimit)) {
417             $sql = str_replace('{limit}',$addLimit,$sql);
418             //}
419         }
420
421         if (! empty($options ['bind']) && is_array($options ['bind']) && strstr($sql, '?')) {
422             $sql = array_merge(array ($sql ), $options ['bind']);
423         }
424
425         $result = & $this->_findBySqlWithAssociations($sql, empty($options ['virtual_limit']) ? false : $options ['virtual_limit'], $load_acts, $returns,$simulation_class, $config);
426         if (empty($result)) {
427             $result = false;
428         }
429         return $result;
430     }
431
432
433     public function _prepareIncludes($prefix,$parent_is_plural, &$parent,&$available_associated_options,$handler_name,$parent_association_id,$association_id,&$options,&$association_options, &$replacements, &$config)
434     {
435         if (isset($association_options['include'])) {
436             $association_options['include'] = Ak::toArray($association_options['include']);
437             if (isset($parent->$handler_name) && method_exists($parent->$handler_name,'getModelName')) {
438                 $main_association_class_name = $parent->$handler_name->getModelName();
439                 Ak::import($main_association_class_name);
440                 $sub_association_object = new $main_association_class_name;
441             } else if (isset($parent->$handler_name) && method_exists($parent->$handler_name,'getAssociatedModelInstance')){
442                 $sub_association_object = &$parent->$handler_name->getAssociatedModelInstance();
443             } else {
444                 $sub_association_object = &$parent;
445             }
446
447         } else {
448             /**
449              * No included associations
450              */
451             return;
452         }
453
454         foreach ( $association_options ['include'] as $idx=>$sub_association_id ) {
455             if (!is_numeric($idx) && is_array($sub_association_id)) {
456                 $sub_options = $sub_association_id;
457
458                 $sub_association_id = $idx;
459             } else {
460                 $sub_options = array();
461             }
462
463             $sub_handler_name = $sub_association_object->getCollectionHandlerName($sub_association_id);
464
465             if (!$sub_handler_name) {
466                 $sub_handler_name = $sub_association_id;
467             }
468
469             $type = $sub_association_object->$sub_handler_name->getType();
470
471             if ($type == 'hasMany' || $type ==
472             'hasAndBelongsToMany') {
473                 $instance=&$sub_association_object->$sub_handler_name->getAssociatedModelInstance();
474                 $class_name = $instance->getType();
475                 $table_name = $instance->getTableName();
476                 $pk = $instance->getPrimaryKey();
477                 $pluralize = true;
478             } else if ( $type == 'belongsTo' || $type == 'hasOne') {
479                 $class_name = $sub_association_object->$sub_handler_name->getAssociationOption('class_name');
480                 if(!class_exists($class_name)) {
481                     Ak::import($class_name);
482                 }
483                 $instance = new $class_name;
484                 $table_name = $instance->getTableName();
485
486                 $pk = $instance->getPrimaryKey();
487                 $pluralize = false;
488             } else {
489                 $pk = $sub_association_object->$sub_handler_name->getPrimaryKey();
490                 $instance = &$sub_association_object;
491                 $class_name =$instance->getType();
492                 $pluralize = false;
493                 $table_name = $instance->getTableName();
494             }
495             $config[$handler_name][$sub_handler_name] = array('association_id'=>$sub_association_id,'class'=>$class_name,'pk'=>$pk, 'instance'=>$instance);
496             $sub_associated_options = $sub_association_object->$sub_handler_name->getAssociatedFinderSqlOptionsForInclusionChain($prefix.'['.$handler_name.']'.($parent_is_plural?'[@'.$pk.']':''),'__owner__'.$parent_association_id,
497             $sub_options, $pluralize);
498
499             /**
500                      * Adding replacements for base options like order,conditions,group.
501                      * The table-aliases of the included associations will be replaced
502                      * with their respective __owner_$handler_name.$column_name representative.
503                      */
504             $replacements['/([,\s])_('.$sub_association_id.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
505             $replacements['/([,\s])('.$sub_association_id.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
506             $replacements['/([,\s])_('.$table_name.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
507             $replacements['/([,\s])('.$table_name.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
508             $replacements['/([,\s])_('.$sub_handler_name.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
509             $replacements['/([,\s])('.$sub_handler_name.')\./']='\\1__owner__'.$parent_association_id.'__'.$sub_handler_name.'.';
510
511
512             foreach ( array_keys(
513             $available_associated_options) as $sub_associated_option ) {
514
515                 $newoption=isset($sub_associated_options [$sub_associated_option])?$sub_associated_options [$sub_associated_option]:'';
516                 if ($sub_associated_option!='bind' && $sub_associated_option!='include') {
517                     $newoption=trim($newoption);
518                     if(!empty($newoption)) {
519                         $available_associated_options [$sub_associated_option] []  = $newoption;
520                     }
521                 } else {
522                     $available_associated_options [$sub_associated_option] = array_merge((array)$available_associated_options [$sub_associated_option],Ak::toArray($newoption));
523                 }
524
525             }
526             if (!empty($sub_options)) {
527                 $this->_prepareIncludes($prefix.'['.$handler_name.']'.($parent_is_plural?'[@'.$pk.']':''),$pluralize,$instance,$available_associated_options,$sub_handler_name,$parent_association_id.'__'.$sub_handler_name,$sub_association_id,$options['include'][$association_id],$association_options['include'][$idx],$replacements,$config[$handler_name]);
528             }
529         }
530     }
531
532     public function constructCalculationSqlWithAssociations($sql, $options = array())
533     {
534         $calculation_function = isset($options['calculation']) && isset($options['calculation']['function'])?$options['calculation']['function']:'count';
535         $calculation_column = isset($options['calculation']) && isset($options['calculation']['column'])?$options['calculation']['column']:'*';
536         $calculation_alias = isset($options['calculation']) && isset($options['calculation']['alias'])?$options['calculation']['alias']:'count_all';
537
538         $selection = $calculation_function.'( '.$calculation_column.' ) AS '.$calculation_alias.' ';
539
540         $sql = preg_replace('/SELECT (.*?) FROM/i','SELECT '.$selection. ' FROM', $sql);
541         $groupBy = 'GROUP BY __owner.id';
542         if (preg_match('/GROUP BY (.*?)($|ORDER)/i',$sql,$matches)) {
543             $sql = str_replace($matches[1],'__owner.id',$sql);
544         }
545         return $sql;
546     }
547
548     public function &_calculateBySqlWithAssociations($sql)
549     {
550         $objects = array();
551         $results = $this->_db->execute ($sql,'find with associations');
552         if (!$results){
553             return $objects;
554         }
555         return $results;
556     }
557
558     public function &_findBySqlWithAssociations($sql, $virtual_limit = false, $load_acts = true, $returns = 'default', $simulation_class = 'AkActiveRecordMock', $config = array())
559     {
560         $objects = array();
561         $results = $this->_db->execute ($sql,'find with associations ext');
562         if (!$results){
563             return $objects;
564         }
565
566         $result =& $this->_generateObjectGraphFromResultSet($results,$virtual_limit, $load_acts, $returns, $simulation_class, $config);
567         return $result;
568
569     }
570     /**
571      * Generates objects from special sql:
572      * SELECT id as owner[id]...
573      *
574      *
575      *
576      * @param ADOResultSet $results            a result set from Db->execute
577      * @param array $included_associations     just like in ->find(); $options['include']; but in fact unused
578      * @param mixed $virtual_limit             int or false; unsure if this works
579      * @return array                           ObjectGraph as an array
580      */
581     public function &_generateObjectGraphFromResultSet($results, $virtual_limit = false, $load_acts=true, $returns = 'default',$simulation_class='AkActiveRecordMock', $config = array())
582     {
583         $return = array();
584         $owner = array();
585         $keys = array();
586         $record_counter=0;
587         while ($record = $results->FetchRow()) {
588             $record_counter++;
589             /**
590              * implement limits here, config should have limits per association
591              * need offset as well
592              */
593             foreach($record as $key=>$value) {
594
595                 if (strstr($key,'@')) {
596                     $true=true;
597                     while($true) {
598                         if (!isset($keys[$key])) {
599                             $pos=@strrpos($key,'@');
600                             $length = @strpos(']',$key,$pos);
601                             $pk = @substr($key,$pos+1,$length+2);
602                             $kpos=@strpos(']',$key,$pos);
603                             $base = @substr($key,0,$kpos+$pos-1);
604                             $replace = $base.'[@'.$pk.']';
605                             $subkey = $replace.'['.$pk.']';
606                             $keys[$key] = array('pos'=>$pos,'length'=>$length,'pk'=>$pk,'kpos'=>$kpos,'subkey'=>$subkey,'replace'=>$replace,'base'=>$base);
607                         } else {
608                             $subkey = $keys[$key]['subkey'];
609                             $pos=$keys[$key]['pos'];
610                             $kpos=$keys[$key]['kpos'];
611                             $pk=$keys[$key]['pk'];
612                             $replace=$keys[$key]['replace'];
613                             $base=$keys[$key]['base'];
614                         }
615                         if (isset($record[$subkey])) {
616                             $id = $record[$subkey];
617                         } else {
618                             $id = 0;
619                         }
620                         $key = str_replace($replace,$base.'['.$id.']',$key);
621
622                         if(!strstr($key,'@')) {
623                             $true=false;
624                         }
625                     }
626                 }
627
628                 $this->_addToOwner($owner,str_replace('owner[','[',$key),$value, $returns, $config['__owner']);
629
630             }
631             unset($record);
632         }
633         if ($returns == 'default') {
634             unset($keys);
635             if (!empty($owner)) {
636                 $available_attributes = $this->getAvailableAttributes();
637                 $available_attributes = array_keys($available_attributes);
638                 foreach($owner as $id=>$data) {
639
640                     if (!isset($diff)) {
641                         $diff = @array_diff(array_keys($data),$available_attributes);
642                         $nondiff = array();
643                         foreach($diff as $d) {
644                             $nondiff[$d] = null;
645                         }
646                     }
647                     $available array_merge($data,$nondiff);
648
649                     $available['load_associations'] = false;
650                     $available['load_acts'] = $load_acts;
651
652                     $obj=&$this->instantiate($available,false,false);
653
654                     foreach(array_values($diff) as $rel) {
655                         $this->_setAssociations($rel,$data[$rel],$obj, $load_acts);
656                     }
657
658                     $obj->afterInstantiate();
659                     $obj->notifyObservers('afterInstantiate');
660                     $return[]=&$obj;
661                 }
662             } else {
663                 $return = false;
664             }
665         } else if ($returns == 'array') {
666
667             $this->_reindexArray($owner);
668             $return = $owner;
669         } else if ($returns == 'simulated') {
670             include_once AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActiveRecordMock.php';
671             $false = false;
672             $return = &$this->_generateStdClasses($simulation_class,$owner, $this->getType(), $false, $false, $config);
673         }
674         return $return;
675     }
676     public function _reindexArray(&$array)
677     {
678         if (is_numeric(key($array))) {
679             $array = array_values($array);
680         }
681         foreach($array as $key => $value) {
682             if (is_array($value)) {
683                 $this->_reindexArray($array[$key]);
684             }
685         }
686     }
687     public function _generateStdClasses($simulation_class,$owner, $class, $handler_name, &$parent, $config = array(), $config_key = '__owner')
688     {
689         /**echo "<pre>";
690         var_dump($config_key);
691         var_dump($config);
692         echo "</pre>";*/
693
694         $return = array();
695         $singularize=false;
696         $pk = isset($config[$config_key]['pk'])?$config[$config_key]['pk']:'id';
697         if (!is_numeric(key($owner))) {
698             $singularize =true;
699
700             $key = isset($owner[$pk])?$owner[$pk]:0;
701             $owner = array($key=>$owner);
702         }
703         if(is_array($owner))
704         foreach($owner as $id=>$data) {
705             require_once AK_LIB_DIR.DS.'AkActiveRecord'.DS.'AkActiveRecordMock.php';
706             $id = isset($data[$pk])?$data[$pk]:$id;
707             $obj =& new $simulation_class($id,$class, $handler_name, $parent);
708             if(is_array($data))
709             foreach($data as $key => $value) {
710                 if ($key{0}=='_') continue;
711                 if ( is_scalar($value)) {
712                     $obj->$key = $value;
713                 } else if (is_array($value)) {
714                     //$assoc = $key;
715                     $assoc = isset($config[$config_key][$key]['association_id'])?$config[$config_key][$key]['association_id']:false;
716                     //if(is_numeric(key($value))) {
717                     //    $key = AkInflector::pluralize($key);
718                     //}
719                     /** if ($handler_name!==false) {
720                         $config_key = '__owner';
721                     } else {
722                         $config_key = $handler_name;
723                     }*/
724                     if ($assoc) {
725                         $obj->$assoc = &$this->_generateStdClasses($simulation_class,$value, @$config[$config_key][$key]['class'], $key,$obj, @$config[$config_key], $key);
726                         //$this->log('setting assoc:'.$assoc);
727                         $obj->_addAssociation($assoc, $key);
728                     } else {
729                         /**$assoc = $key;
730                         $obj->$assoc = &$this->_generateStdClasses($simulation_class,$value, @$config[$config_key][$key]['class'], $key,$obj, @$config[$config_key], $key);
731                         $this->log('setting assoc with key:'.$assoc);
732                         $obj->_addAssociation($assoc, $key);*/
733                         //die('fuck');
734                         /**var_dump($key);
735                         var_dump($value);
736                         var_dump($config_key);
737                         var_dump($config);*/
738                         //var_dump(func_get_args());
739                     }
740                 }
741             }
742             $return[]=&$obj;
743         }
744         return $singularize?$return[0]:$return;
745     }
746
747
748     public function org_addToOwner(&$owner, $key, $value) {
749
750         if(preg_match_all('/\[(.*?)\]/',$key,$matches)) {
751             $count = count($matches[1]);
752             $last = &$owner;
753             for($idx=0;$idx<$count;$idx++) {
754
755                 if (!isset($last[$matches[1][$idx]])) {
756                     $last[$matches[1][$idx]] = array();
757                 }
758                 $last = &$last[$matches[1][$idx]];
759
760             }
761             $last = $value;
762         }
763     }
764     public function _addToOwner(&$owner, $key, $value, $returns, $config) {
765
766         if(preg_match_all('/\[(.*?)\]/',$key,$matches)) {
767             $count = count($matches[1]);
768             $last = &$owner;
769             for($idx=0;$idx<$count;$idx++) {
770                 $key = $matches[1][$idx];
771                 $association = $key;
772
773                 if (isset($config[$key])) {
774                     $config = $config[$key];
775                     if($returns=='array') {
776                         $association = $config['association_id'];
777                     }
778                     //$this->log('using association:'.$association);
779                 }
780                 if (!isset($last[$association])) {
781                     $last[$association] = array();
782                 }
783                 $last = &$last[$association];
784
785             }
786             if($returns=='array') {
787                 $value=$this->_castAttributeFromDatabase($association,$value,$config['instance']);
788             }
789             $last = $value;
790         }
791         //$this->log('owner:'.var_export($owner,true));
792     }
793
794     public function _setAssociations($assoc_name, $val, &$parent, $load_acts = true) {
795         static $instances = array();
796         static $instance_attributes = array();
797         if ($assoc_name{0}=='_') return;
798         if (method_exists($parent,'getAssociationOption')) {
799             $class=$parent->getType();
800
801             $instance = new $class;
802
803             if (isset($instance->$assoc_name) && method_exists($instance->$assoc_name,'getAssociationOption')) {
804                 $class = $instance->$assoc_name->getAssociationOption('class_name');
805                 if (!isset($instances[$class])) {
806                     $instance = new $class;
807                     $instances[$class] = &$instance;
808                 } else {
809                     $instance = &$instances[$class];
810                 }
811             } else if (isset($parent->$assoc_name) && method_exists($parent->$assoc_name,'getAssociatedModelInstance')){
812                 if (!isset($instances[$parent->getType().'-'.$assoc_name])) {
813                     $instance = $parent->$assoc_name->getAssociatedModelInstance();
814                     $instances[$parent->getType().'-'.$assoc_name] = &$instance;
815                 } else {
816                     $instance=&$instances[$parent->getType().'-'.$assoc_name];
817                 }
818             } else if (isset($parent->$assoc_name) && method_exists($parent->$assoc_name,'getType') && !in_array($parent->$assoc_name->getType(),array('belongsTo','hasOne','hasOne','hasMany','hasAndBelongsToMany'))) {
819
820                 $instance = $parent->$assoc_name;
821             } else if (isset($instance->$assoc_name)) {
822                 if (!isset($instances[$instance->getType().'-'.$assoc_name])) {
823                     $instance = $instance->$assoc_name->getAssociatedModelInstance();
824                     $instances[$instance->getType().'-'.$assoc_name] = &$instance;
825                 } else {
826                     $instance=&$instances[$instance->getType().'-'.$assoc_name];
827                 }
828             } else {
829                 $this->log('Cannot find association:'.$assoc_name.' on '.$parent->getType());
830                 return;
831             }
832
833         } else {
834             if (!$parent->$assoc_name) {
835                 $this->log($parent->getType().'->'.$assoc_name.' does not have assoc');
836                 return;
837             }
838             if (!isset($instances[$parent->getType().'-'.$assoc_name])) {
839                 $instance = $parent->$assoc_name->getAssociatedModelInstance();
840                 $instances[$parent->getType().'-'.$assoc_name]=&$instance;
841             } else {
842                 $instance=&$instances[$parent->getType().'-'.$assoc_name];
843             }
844         }
845
846         if (is_numeric(key($val))) {
847             $owner =$val;
848         } else {
849             $owner = array($val);
850         }
851         if (!isset($instance_attributes[$instance->getType()])) {
852             $available_attributes = $instance->getAvailableAttributes();
853             $available_attributes = array_keys($available_attributes);
854             $instance_attributes[$instance->getType()] = $available_attributes;
855         } else {
856             $available_attributes=$instance_attributes[$instance->getType()];
857         }
858
859         foreach($owner as $data) {
860
861             if (!isset($diff)) {
862                 $diff = @array_diff(@array_keys($data),$available_attributes);
863                 $nondiff = array();
864                 if(is_array($diff)) {
865                     foreach(array_keys($diff) as $d) {
866                         $nondiff[$d] = null;
867                     }
868                 }
869             }
870             $available = @array_merge($data,$nondiff);
871
872             if(empty($available[$instance->getPrimaryKey()])) {
873                 $parent->$assoc_name->_loaded=true;
874                 //return;
875                 continue;
876             }
877             $available['load_associations'] = false;
878             $available['load_acts'] = $load_acts;
879
880             $available = $this->_castAttributesFromDatabase($available,$instance);
881             $obj=&$parent->$assoc_name->build($available,false);
882
883             $obj->_newRecord = false;
884             $parent->$assoc_name->_loaded=true;
885             $obj->_loaded=true;
886             if(is_array($diff)) {
887                 foreach(array_values($diff) as $rel) {
888                     $this->_setAssociations($rel,$data[$rel],$obj);
889                 }
890             }
891
892         }
893     }
894     public function _castAttributesFromDatabase($attributes = array(),$record = null)
895     {
896         foreach($attributes as $key => $value) {
897             $attributes[$key] = $this->_castAttributeFromDatabase($key,$value,$record);
898         }
899         return $attributes;
900     }
901     public function _castAttributeFromDatabase($column_name,$value, $record)
902     {
903         $column_type = $record->getColumnType($column_name);
904
905         if($column_type){
906             if('integer' == $column_type){
907                 return is_null($value) ? null : (integer)$value;
908                 //return is_null($value) ? null : $value;    // maybe for bigint we can do this
909             }elseif('boolean' == $column_type){
910                 if (is_null($value)) {
911                     return null;
912                 }
913                 if ($this->_getDatabaseType()=='postgre'){
914                     return $value=='t' ? true : false;
915                 }
916                 return (integer)$value === 1 ? true : false;
917             }elseif(!empty($value) && 'date' == $column_type && strstr(trim($value),' ')){
918                 return substr($value,0,10) == '0000-00-00' ? null : str_replace(substr($value,strpos($value,' ')), '', $value);
919             }elseif (!empty($value) && 'datetime' == $column_type && substr($value,0,10) == '0000-00-00'){
920                 return null;
921             }elseif ('binary' == $column_type && $this->_getDatabaseType() == 'postgre'){
922                 $value = $this->_db->unescape_blob($value);
923                 $value = empty($value) || trim($value) == 'null' ? null : $value;
924             }elseif($record->_shouldSerializeColumn($column_name)){
925                 $this->_ensureClassExistsForSerializedColumnBeforeUnserializing($column_name);
926                 $value = @unserialize($value);
927             }
928         }
929         return $value;
930     }
931     public function getCollectionHandlerName($association_id)
932     {
933         if(isset($this->$association_id) && is_object($this->$association_id) && method_exists($this->$association_id,'getAssociatedFinderSqlOptions')){
934             return false;
935         }
936         $collection_handler_name = AkInflector::singularize($association_id);
937         if(isset($this->$collection_handler_name) &&
938         is_object($this->$collection_handler_name)  &&
939         in_array($this->$collection_handler_name->getType(),array('hasMany','hasAndBelongsToMany'))){
940             return $collection_handler_name;
941         } else if (isset($this->_associationIds[$association_id])) {
942             return $this->_associationIds[$association_id];
943         } else{
944             return false;
945         }
946     }
947
948
949     /**
950      * Used for generating custom selections for habtm, has_many and has_one queries
951      */
952     public function constructFinderSqlWithAssociations($options, $include_owner_as_selection = true)
953     {
954         $sql = 'SELECT ';
955         $selection = '';
956         $parent_pk = $this->getPrimaryKey();
957         $parenthesis = $this->_db->type()=='mysql'?"'":'"';
958         if($include_owner_as_selection){
959             foreach (array_keys($this->getColumns()) as $column_name){
960                 $selection .= '__owner.'.$column_name.' AS '.$parenthesis.'owner[@'.$parent_pk.']['.$column_name.']'.$parenthesis.', ';
961             }
962             $selection .= (isset($options['selection']) ? $options['selection'].' ' : '');
963             $selection = trim($selection,', ').' '; // never used by the unit tests
964         }else{
965             // used only by HasOne::findAssociated
966             $selection .= $options['selection'].'.* ';
967         }
968         $sql .= $selection;
969         $sql .= 'FROM '.($include_owner_as_selection ? $this->getTableName().' AS __owner ' : $options['selection'].' ');
970         $sql .= (!empty($options['joins']) ? $options['joins'].' ' : '');
971
972         empty($options['conditions']) ? null : $this->addConditions($sql, $options['conditions'], $include_owner_as_selection?'__owner':null);
973
974         // Create an alias for order
975         if(empty($options['order']) && !empty($options['sort'])){
976             $options['order'] = $options['sort'];
977         }
978         $sql  .= !empty($options['group']) ? ' GROUP BY  '.$options['group'] : '';
979         $sql  .= !empty($options['order']) ? ' ORDER BY  '.$options['order'] : '';
980
981         $this->_db->addLimitAndOffset($sql,$options);
982         return $sql;
983     }
984
985
986
987     public function _addTableAliasesToAssociatedSqlWithAlias($add_alias, $alias,$sql)
988     {
989         return preg_replace($this->getColumnsWithRegexBoundariesAndAlias($alias),'\1'.$add_alias.'.\3',' '.$sql.' ');
990     }
991
992     public function _addTableAliasesToAssociatedSql($table_alias, $sql)
993     {
994         return preg_replace($this->getColumnsWithRegexBoundaries(),'\1'.$table_alias.'.\2',' '.$sql.' ');
995     }
996
997 }
998
999 ?>
1000
Note: See TracBrowser for help on using the browser.