[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/AkActiveRecord/ -> AkAssociatedActiveRecord.php (source)

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


Generated: Mon Oct 27 12:43:49 2008 Cross-referenced by PHPXref 0.6