MODX Revolution PHP Cross Reference Content Management Systems

Source: /core/xpdo/om/xpdoobject.class.php - 2539 lines - 114566 bytes - Summary - Text - Print

Description: The base persistent xPDO object classes. This file contains the base persistent object classes, which your user- defined classes will extend when implementing an xPDO object model.

   1  <?php
   2  /*
   3   * Copyright 2010-2013 by MODX, LLC.
   4   *
   5   * This file is part of xPDO.
   6   *
   7   * xPDO is free software; you can redistribute it and/or modify it under the
   8   * terms of the GNU General Public License as published by the Free Software
   9   * Foundation; either version 2 of the License, or (at your option) any later
  10   * version.
  11   *
  12   * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15   *
  16   * You should have received a copy of the GNU General Public License along with
  17   * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18   * Suite 330, Boston, MA 02111-1307 USA
  19   */
  20  
  21  /**
  22   * The base persistent xPDO object classes.
  23   *
  24   * This file contains the base persistent object classes, which your user-
  25   * defined classes will extend when implementing an xPDO object model.
  26   *
  27   * @package xpdo
  28   * @subpackage om
  29   */
  30  
  31  /**
  32   * The base persistent xPDO object class.
  33   *
  34   * This is the basis for the entire xPDO object model, and can also be used by a
  35   * class generator {@link xPDOGenerator}, ultimately allowing custom classes to
  36   * be user-defined in a web interface and framework-generated at runtime.
  37   *
  38   * @abstract This is an abstract class, and is not represented by an actual
  39   * table; it simply defines the member variables and functions needed for object
  40   * persistence.
  41   *
  42   * @package xpdo
  43   * @subpackage om
  44   */
  45  class xPDOObject {
  46      /**
  47       * A convenience reference to the xPDO object.
  48       * @var xPDO
  49       * @access public
  50       */
  51      public $xpdo= null;
  52  
  53      /**
  54       * Name of the data source container the object belongs to.
  55       * @var string
  56       * @access public
  57       */
  58      public $container= null;
  59  
  60      /**
  61       * Names of the fields in the data table, fully-qualified with a table name.
  62       *
  63       * NOTE: For use in table joins to qualify fields with the same name.
  64       *
  65       * @var array
  66       * @access public
  67       */
  68      public $fieldNames= null;
  69  
  70      /**
  71       * The actual class name of an instance.
  72       * @var string
  73       */
  74      public $_class= null;
  75  
  76      /**
  77       * The package the class is a part of.
  78       * @var string
  79       */
  80      public $_package= null;
  81  
  82      /**
  83       * An alias for this instance of the class.
  84       * @var string
  85       */
  86      public $_alias= null;
  87  
  88      /**
  89       * The primary key field (or an array of primary key fields) for this object.
  90       * @var string|array
  91       * @access public
  92       */
  93      public $_pk= null;
  94  
  95      /**
  96       * The php native type of the primary key field.
  97       *
  98       * NOTE: Will be an array if multiple primary keys are specified for the object.
  99       *
 100       * @var string|array
 101       * @access public
 102       */
 103      public $_pktype= null;
 104  
 105      /**
 106       * Name of the actual table representing this class.
 107       * @var string
 108       * @access public
 109       */
 110      public $_table= null;
 111  
 112      /**
 113       * An array of meta data for the table.
 114       * @var string
 115       * @access public
 116       */
 117      public $_tableMeta= null;
 118  
 119      /**
 120       * An array of field names that have been modified.
 121       * @var array
 122       * @access public
 123       */
 124      public $_dirty= array ();
 125  
 126      /**
 127       * An array of field names that have not been loaded from the source.
 128       * @var array
 129       * @access public
 130       */
 131      public $_lazy= array ();
 132  
 133      /**
 134       * An array of key-value pairs representing the fields of the instance.
 135       * @var array
 136       * @access public
 137       */
 138      public $_fields= array ();
 139  
 140      /**
 141       * An array of metadata definitions for each field in the class.
 142       * @var array
 143       * @access public
 144       */
 145      public $_fieldMeta= array ();
 146  
 147      /**
 148       * An optional array of field aliases.
 149       * @var array
 150       */
 151      public $_fieldAliases= array();
 152  
 153      /**
 154       * An array of aggregate foreign key relationships for the class.
 155       * @var array
 156       * @access public
 157       */
 158      public $_aggregates= array ();
 159  
 160      /**
 161       * An array of composite foreign key relationships for the class.
 162       * @var array
 163       * @access public
 164       */
 165      public $_composites= array ();
 166  
 167      /**
 168       * An array of object instances related to this object instance.
 169       * @var array
 170       * @access public
 171       */
 172      public $_relatedObjects= array ();
 173  
 174      /**
 175       * A validator object responsible for this object instance.
 176       * @var xPDOValidator
 177       * @access public
 178       */
 179      public $_validator = null;
 180  
 181      /**
 182       * An array of validation rules for this object instance.
 183       * @var array
 184       * @access public
 185       */
 186      public $_validationRules = array();
 187  
 188      /**
 189       * An array of field names that have been already validated.
 190       * @var array
 191       * @access public
 192       */
 193      public $_validated= array ();
 194  
 195      /**
 196       * Indicates if the validation map has been loaded.
 197       * @var boolean
 198       * @access public
 199       */
 200      public $_validationLoaded= false;
 201  
 202      /**
 203       * Indicates if the instance is transient (and thus new).
 204       * @var boolean
 205       * @access public
 206       */
 207      public $_new= true;
 208  
 209      /**
 210       * Indicates the cacheability of the instance.
 211       * @var boolean
 212       */
 213      public $_cacheFlag= true;
 214  
 215      /**
 216       * A collection of various options that can be used on the instance.
 217       * @var array
 218       */
 219      public $_options= array();
 220  
 221      /**
 222       * Responsible for loading a result set from the database.
 223       *
 224       * @static
 225       * @param xPDO &$xpdo A valid xPDO instance.
 226       * @param string $className Name of the class.
 227       * @param xPDOCriteria $criteria A valid xPDOCriteria instance.
 228       * @return PDOStatement A reference to a PDOStatement representing the
 229       * result set.
 230       */
 231      public static function & _loadRows(& $xpdo, $className, $criteria) {
 232          $rows= null;
 233          if ($criteria->prepare()) {
 234              if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Attempting to execute query using PDO statement object: " . print_r($criteria->sql, true) . print_r($criteria->bindings, true));
 235              $tstart= microtime(true);
 236              if (!$criteria->stmt->execute()) {
 237                  $xpdo->queryTime += microtime(true) - $tstart;
 238                  $xpdo->executedQueries++;
 239                  $errorInfo= $criteria->stmt->errorInfo();
 240                  $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error ' . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($errorInfo, true));
 241                  if (($errorInfo[1] == '1146' || $errorInfo[1] == '1') && $xpdo->getOption(xPDO::OPT_AUTO_CREATE_TABLES)) {
 242                      if ($xpdo->getManager() && $xpdo->manager->createObjectContainer($className)) {
 243                          $tstart= microtime(true);
 244                          if (!$criteria->stmt->execute()) {
 245                              $xpdo->queryTime += microtime(true) - $tstart;
 246                              $xpdo->executedQueries++;
 247                              $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($criteria->stmt->errorInfo(), true));
 248                          } else {
 249                              $xpdo->queryTime += microtime(true) - $tstart;
 250                              $xpdo->executedQueries++;
 251                          }
 252                      } else {
 253                          $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $xpdo->errorCode() . " attempting to create object container for class {$className}:\n" . print_r($xpdo->errorInfo(), true));
 254                      }
 255                  }
 256              } else {
 257                  $xpdo->queryTime += microtime(true) - $tstart;
 258                  $xpdo->executedQueries++;
 259              }
 260              $rows= & $criteria->stmt;
 261          } else {
 262              $errorInfo = $xpdo->errorInfo();
 263              $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error preparing statement for query: {$criteria->sql} - " . print_r($errorInfo, true));
 264              if (($errorInfo[1] == '1146' || $errorInfo[1] == '1') && $xpdo->getOption(xPDO::OPT_AUTO_CREATE_TABLES)) {
 265                  if ($xpdo->getManager() && $xpdo->manager->createObjectContainer($className)) {
 266                      if (!$criteria->prepare()) {
 267                          $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error preparing statement for query: {$criteria->sql} - " . print_r($errorInfo, true));
 268                      } else {
 269                          $tstart= microtime(true);
 270                          if (!$criteria->stmt->execute()) {
 271                              $xpdo->queryTime += microtime(true) - $tstart;
 272                              $xpdo->executedQueries++;
 273                              $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($criteria->stmt->errorInfo(), true));
 274                          } else {
 275                              $xpdo->queryTime += microtime(true) - $tstart;
 276                              $xpdo->executedQueries++;
 277                          }
 278                      }
 279                  } else {
 280                      $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $xpdo->errorCode() . " attempting to create object container for class {$className}:\n" . print_r($xpdo->errorInfo(), true));
 281                  }
 282              }
 283          }
 284          return $rows;
 285      }
 286  
 287      /**
 288       * Loads an instance from an associative array.
 289       *
 290       * @static
 291       * @param xPDO &$xpdo A valid xPDO instance.
 292       * @param string $className Name of the class.
 293       * @param xPDOQuery|string $criteria A valid xPDOQuery instance or relation alias.
 294       * @param array $row The associative array containing the instance data.
 295       * @return xPDOObject A new xPDOObject derivative representing a data row.
 296       */
 297      public static function _loadInstance(& $xpdo, $className, $criteria, $row) {
 298          $rowPrefix= '';
 299          if (is_object($criteria) && $criteria instanceof xPDOQuery) {
 300              $alias = $criteria->getAlias();
 301              $actualClass = $criteria->getClass();
 302          } elseif (is_string($criteria) && !empty($criteria)) {
 303              $alias = $criteria;
 304              $actualClass = $className;
 305          } else {
 306              $alias = $className;
 307              $actualClass= $className;
 308          }
 309          if (isset ($row["{$alias}_class_key"])) {
 310              $actualClass= $row["{$alias}_class_key"];
 311              $rowPrefix= $alias . '_';
 312          } elseif (isset($row["{$className}_class_key"])) {
 313              $actualClass= $row["{$className}_class_key"];
 314              $rowPrefix= $className . '_';
 315          } elseif (isset ($row['class_key'])) {
 316              $actualClass= $row['class_key'];
 317          }
 318          /** @var xPDOObject $instance */
 319          $instance= $xpdo->newObject($actualClass);
 320          if (is_object($instance) && $instance instanceof xPDOObject) {
 321              $pk = $xpdo->getPK($actualClass);
 322              if ($pk) {
 323                  if (is_array($pk)) $pk = reset($pk);
 324                  if (isset($row["{$alias}_{$pk}"])) {
 325                      $rowPrefix= $alias . '_';
 326                  }
 327                  elseif ($actualClass !== $className && $actualClass !== $alias && isset($row["{$actualClass}_{$pk}"])) {
 328                      $rowPrefix= $actualClass . '_';
 329                  }
 330                  elseif ($className !== $alias && isset($row["{$className}_{$pk}"])) {
 331                      $rowPrefix= $className . '_';
 332                  }
 333              } elseif (strpos(strtolower(key($row)), strtolower($alias . '_')) === 0) {
 334                  $rowPrefix= $alias . '_';
 335              } elseif (strpos(strtolower(key($row)), strtolower($className . '_')) === 0) {
 336                  $rowPrefix= $className . '_';
 337              }
 338              $parentClass = $className;
 339              $isSubPackage = strpos($className,'.');
 340              if ($isSubPackage !== false) {
 341                  $parentClass = substr($className,$isSubPackage+1);
 342              }
 343              if (!$instance instanceof $parentClass) {
 344                  $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Instantiated a derived class {$actualClass} that is not a subclass of the requested class {$className}");
 345              }
 346              $instance->_lazy= $actualClass !== $className ? array_keys($xpdo->getFieldMeta($actualClass)) : array_keys($instance->_fieldMeta);
 347              $instance->fromArray($row, $rowPrefix, true, true);
 348              $instance->_dirty= array ();
 349              $instance->_new= false;
 350          }
 351          return $instance;
 352      }
 353  
 354      /**
 355       * Responsible for loading an instance into a collection.
 356       *
 357       * @static
 358       * @param xPDO &$xpdo A valid xPDO instance.
 359       * @param array &$objCollection The collection to load the instance into.
 360       * @param string $className Name of the class.
 361       * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
 362       * @param array $row The associative array containing the instance data.
 363       * @param bool $fromCache If the instance is for the cache
 364       * @param bool|int $cacheFlag Indicates if the objects should be cached and
 365       * optionally, by specifying an integer value, for how many seconds.
 366       * @return bool True if a valid instance was loaded, false otherwise.
 367       */
 368      public static function _loadCollectionInstance(xPDO & $xpdo, array & $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag=true) {
 369          $loaded = false;
 370          if ($obj= xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row)) {
 371              if (($cacheKey= $obj->getPrimaryKey()) && !$obj->isLazy()) {
 372                  if (is_array($cacheKey)) {
 373                      $pkval= implode('-', $cacheKey);
 374                  } else {
 375                      $pkval= $cacheKey;
 376                  }
 377                  /* set OPT_CACHE_DB_COLLECTIONS to 2 to cache instances by primary key from collection result sets */
 378                  if ($xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1) == 2 && $xpdo->_cacheEnabled && $cacheFlag) {
 379                      if (!$fromCache) {
 380                          $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
 381                          $xpdo->toCache($pkCriteria, $obj, $cacheFlag);
 382                      } else {
 383                          $obj->_cacheFlag= true;
 384                      }
 385                  }
 386                  $objCollection[$pkval]= $obj;
 387                  $loaded = true;
 388              } else {
 389                  $objCollection[]= $obj;
 390                  $loaded = true;
 391              }
 392          }
 393          return $loaded;
 394      }
 395  
 396      /**
 397       * Load an instance of an xPDOObject or derivative class.
 398       *
 399       * @static
 400       * @param xPDO &$xpdo A valid xPDO instance.
 401       * @param string $className Name of the class.
 402       * @param mixed $criteria A valid primary key, criteria array, or
 403       * xPDOCriteria instance.
 404       * @param boolean|integer $cacheFlag Indicates if the objects should be
 405       * cached and optionally, by specifying an integer value, for how many
 406       * seconds.
 407       * @return object|null An instance of the requested class, or null if it
 408       * could not be instantiated.
 409       */
 410      public static function load(xPDO & $xpdo, $className, $criteria, $cacheFlag= true) {
 411          $instance= null;
 412          $fromCache= false;
 413          if ($className= $xpdo->loadClass($className)) {
 414              if (!is_object($criteria)) {
 415                  $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
 416              }
 417              if (is_object($criteria)) {
 418                  $criteria = $xpdo->addDerivativeCriteria($className, $criteria);
 419                  $row= null;
 420                  if ($xpdo->_cacheEnabled && $criteria->cacheFlag && $cacheFlag) {
 421                      $row= $xpdo->fromCache($criteria, $className);
 422                  }
 423                  if ($row === null || !is_array($row)) {
 424                      if ($rows= xPDOObject :: _loadRows($xpdo, $className, $criteria)) {
 425                          $row= $rows->fetch(PDO::FETCH_ASSOC);
 426                          $rows->closeCursor();
 427                      }
 428                  } else {
 429                      $fromCache= true;
 430                  }
 431                  if (!is_array($row)) {
 432                      if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Fetched empty result set from statement: " . print_r($criteria->sql, true) . " with bindings: " . print_r($criteria->bindings, true));
 433                  } else {
 434                      $instance= xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row);
 435                      if (is_object($instance)) {
 436                          if (!$fromCache && $cacheFlag && $xpdo->_cacheEnabled) {
 437                              $xpdo->toCache($criteria, $instance, $cacheFlag);
 438                              if ($xpdo->getOption(xPDO::OPT_CACHE_DB_OBJECTS_BY_PK) && ($cacheKey= $instance->getPrimaryKey()) && !$instance->isLazy()) {
 439                                  $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
 440                                  $xpdo->toCache($pkCriteria, $instance, $cacheFlag);
 441                              }
 442                          }
 443                          if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Loaded object instance: " . print_r($instance->toArray('', true), true));
 444                      }
 445                  }
 446              } else {
 447                  $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'No valid statement could be found in or generated from the given criteria.');
 448              }
 449          } else {
 450              $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Invalid class specified: ' . $className);
 451          }
 452          return $instance;
 453      }
 454  
 455      /**
 456       * Load a collection of xPDOObject instances.
 457       *
 458       * @static
 459       * @param xPDO &$xpdo A valid xPDO instance.
 460       * @param string $className Name of the class.
 461       * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
 462       * @param boolean|integer $cacheFlag Indicates if the objects should be
 463       * cached and optionally, by specifying an integer value, for how many
 464       * seconds.
 465       * @return array An array of xPDOObject instances or an empty array if no instances are loaded.
 466       */
 467      public static function loadCollection(xPDO & $xpdo, $className, $criteria= null, $cacheFlag= true) {
 468          $objCollection= array ();
 469          $fromCache = false;
 470          if (!$className= $xpdo->loadClass($className)) return $objCollection;
 471          $rows= false;
 472          $fromCache= false;
 473          $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
 474          if (!is_object($criteria)) {
 475              $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
 476          }
 477          if (is_object($criteria)) {
 478              $criteria = $xpdo->addDerivativeCriteria($className, $criteria);
 479          }
 480          if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
 481              $rows= $xpdo->fromCache($criteria);
 482              $fromCache = (is_array($rows) && !empty($rows));
 483          }
 484          if (!$fromCache && is_object($criteria)) {
 485              $rows= xPDOObject :: _loadRows($xpdo, $className, $criteria);
 486          }
 487          if (is_array ($rows)) {
 488              foreach ($rows as $row) {
 489                  xPDOObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
 490              }
 491          } elseif (is_object($rows)) {
 492              $cacheRows = array();
 493              while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
 494                  xPDOObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
 495                  if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $cacheRows[] = $row;
 496              }
 497              if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $rows =& $cacheRows;
 498          }
 499          if (!$fromCache && $xpdo->_cacheEnabled && $collectionCaching > 0 && $cacheFlag && !empty($rows)) {
 500              $xpdo->toCache($criteria, $rows, $cacheFlag);
 501          }
 502          return $objCollection;
 503      }
 504  
 505      /**
 506       * Load a collection of xPDOObject instances and a graph of related objects.
 507       *
 508       * @static
 509       * @param xPDO &$xpdo A valid xPDO instance.
 510       * @param string $className Name of the class.
 511       * @param string|array $graph A related object graph in array or JSON
 512       * format, e.g. array('relationAlias'=>array('subRelationAlias'=>array()))
 513       * or {"relationAlias":{"subRelationAlias":{}}}.  Note that the empty arrays
 514       * are necessary in order for the relation to be recognized.
 515       * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
 516       * @param boolean|integer $cacheFlag Indicates if the objects should be
 517       * cached and optionally, by specifying an integer value, for how many
 518       * seconds.
 519       * @return array An array of xPDOObject instances or an empty array if no instances are loaded.
 520       */
 521      public static function loadCollectionGraph(xPDO & $xpdo, $className, $graph, $criteria, $cacheFlag) {
 522          $objCollection = array();
 523          if ($query= $xpdo->newQuery($className, $criteria, $cacheFlag)) {
 524              $query = $xpdo->addDerivativeCriteria($className, $query);
 525              $query->bindGraph($graph);
 526              $rows = array();
 527              $fromCache = false;
 528              $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
 529              if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
 530                  $rows= $xpdo->fromCache($query);
 531                  $fromCache = !empty($rows);
 532              }
 533              if (!$fromCache) {
 534                  if ($query->prepare()) {
 535                      $tstart = microtime(true);
 536                      if ($query->stmt->execute()) {
 537                          $xpdo->queryTime += microtime(true) - $tstart;
 538                          $xpdo->executedQueries++;
 539                          $objCollection= $query->hydrateGraph($query->stmt, $cacheFlag);
 540                      } else {
 541                          $xpdo->queryTime += microtime(true) - $tstart;
 542                          $xpdo->executedQueries++;
 543                          $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error {$query->stmt->errorCode()} executing query: {$query->sql} - " . print_r($query->stmt->errorInfo(), true));
 544                      }
 545                  } else {
 546                      $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error {$xpdo->errorCode()} preparing statement: {$query->sql} - " . print_r($xpdo->errorInfo(), true));
 547                  }
 548              } elseif (!empty($rows)) {
 549                  $objCollection= $query->hydrateGraph($rows, $cacheFlag);
 550              }
 551          }
 552          return $objCollection;
 553      }
 554  
 555      /**
 556       * Get a set of column names from an xPDOObject for use in SQL queries.
 557       *
 558       * @static
 559       * @param xPDO &$xpdo A reference to an initialized xPDO instance.
 560       * @param string $className The class name to get columns from.
 561       * @param string $tableAlias An optional alias for the table in the query.
 562       * @param string $columnPrefix An optional prefix to prepend to each column name.
 563       * @param array $columns An optional array of field names to include or exclude
 564       * (include is default behavior).
 565       * @param boolean $exclude Determines if any specified columns should be included
 566       * or excluded from the set of results.
 567       * @return string A comma-delimited list of the field names for use in a SELECT clause.
 568       */
 569      public static function getSelectColumns(xPDO & $xpdo, $className, $tableAlias= '', $columnPrefix= '', $columns= array (), $exclude= false) {
 570          $columnarray= array ();
 571          $aColumns= $xpdo->getFields($className);
 572          if ($aColumns) {
 573              if (!empty ($tableAlias)) {
 574                  $tableAlias= $xpdo->escape($tableAlias);
 575                  $tableAlias.= '.';
 576              }
 577              if (!$exclude && !empty($columns)) {
 578                  foreach ($columns as $column) {
 579                      if (!in_array($column, array_keys($aColumns))) {
 580                          continue;
 581                      }
 582                      $columnarray[$column]= "{$tableAlias}" . $xpdo->escape($column);
 583                      if (!empty ($columnPrefix)) {
 584                          $columnarray[$column]= $columnarray[$column] . " AS " . $xpdo->escape("{$columnPrefix}{$column}");
 585                      }
 586                  }
 587              } else {
 588                  foreach (array_keys($aColumns) as $k) {
 589                      if ($exclude && in_array($k, $columns)) {
 590                          continue;
 591                      }
 592                      elseif (empty ($columns)) {
 593                          $columnarray[$k]= "{$tableAlias}" . $xpdo->escape($k);
 594                      }
 595                      elseif ($exclude || in_array($k, $columns)) {
 596                          $columnarray[$k]= "{$tableAlias}" . $xpdo->escape($k);
 597                      } else {
 598                          continue;
 599                      }
 600                      if (!empty ($columnPrefix)) {
 601                          $columnarray[$k]= $columnarray[$k] . " AS " . $xpdo->escape("{$columnPrefix}{$k}");
 602                      }
 603                  }
 604              }
 605          }
 606          return implode(', ', $columnarray);
 607      }
 608  
 609      /**
 610       * Constructor
 611       *
 612       * Do not call the constructor directly; see {@link xPDO::newObject()}.
 613       *
 614       * All derivatives of xPDOObject must redeclare this method, and must call
 615       * the parent method explicitly before any additional logic is executed, e.g.
 616       *
 617       * <code>
 618       * public function __construct(xPDO & $xpdo) {
 619       *     parent  :: __construct($xpdo);
 620       *     // Any additional constructor tasks here
 621       * }
 622       * </code>
 623       *
 624       * @access public
 625       * @param xPDO &$xpdo A reference to a valid xPDO instance.
 626       * @return xPDOObject
 627       */
 628      public function __construct(xPDO & $xpdo) {
 629          $this->xpdo= & $xpdo;
 630          $this->container= $xpdo->config['dbname'];
 631          $this->_class= get_class($this);
 632          $pos= strrpos($this->_class, '_');
 633          if ($pos !== false && substr($this->_class, $pos + 1) == $xpdo->config['dbtype']) {
 634              $this->_class= substr($this->_class, 0, $pos);
 635          }
 636          $this->_package= $xpdo->getPackage($this->_class);
 637          $this->_alias= $this->_class;
 638          $this->_table= $xpdo->getTableName($this->_class);
 639          $this->_tableMeta= $xpdo->getTableMeta($this->_class);
 640          $this->_fields= $xpdo->getFields($this->_class);
 641          $this->_fieldMeta= $xpdo->getFieldMeta($this->_class);
 642          $this->_fieldAliases= $xpdo->getFieldAliases($this->_class);
 643          $this->_aggregates= $xpdo->getAggregates($this->_class);
 644          $this->_composites= $xpdo->getComposites($this->_class);
 645          if ($relatedObjs= array_merge($this->_aggregates, $this->_composites)) {
 646              foreach ($relatedObjs as $aAlias => $aMeta) {
 647                  if (!array_key_exists($aAlias, $this->_relatedObjects)) {
 648                      if ($aMeta['cardinality'] == 'many') {
 649                          $this->_relatedObjects[$aAlias]= array ();
 650                      }
 651                      else {
 652                          $this->_relatedObjects[$aAlias]= null;
 653                      }
 654                  }
 655              }
 656          }
 657          foreach ($this->_fieldAliases as $fieldAlias => $field) {
 658              $this->addFieldAlias($field, $fieldAlias);
 659          }
 660          $this->setDirty();
 661      }
 662  
 663      /**
 664       * Add an alias as a reference to an actual field of the object.
 665       *
 666       * @param string $field The field name to create a reference to.
 667       * @param string $alias The name of the reference.
 668       * @return bool True if the reference is added successfully.
 669       */
 670      public function addFieldAlias($field, $alias) {
 671          $added = false;
 672          if (array_key_exists($field, $this->_fields)) {
 673              if (!array_key_exists($alias, $this->_fields)) {
 674                  $this->_fields[$alias] =& $this->_fields[$field];
 675                  if (!array_key_exists($alias, $this->_fieldAliases)) {
 676                      $this->_fieldAliases[$alias] = $field;
 677                      if (!array_key_exists($alias, $this->xpdo->map[$this->_class]['fieldAliases'])) {
 678                          $this->xpdo->map[$this->_class]['fieldAliases'][$alias]= $field;
 679                      }
 680                  }
 681                  $added = true;
 682              } else {
 683                  $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "The alias {$alias} is already in use as a field name in objects of class {$this->_class}", '', __METHOD__, __FILE__, __LINE__);
 684              }
 685          }
 686          return $added;
 687      }
 688  
 689      /**
 690       * Get an option value for this instance.
 691       *
 692       * @param string $key The option key to retrieve a value for.
 693       * @param array|null $options An optional array to search for a value in first.
 694       * @param mixed $default A default value to return if no value is found; null is the default.
 695       * @return mixed The value of the option or the provided default if it is not set.
 696       */
 697      public function getOption($key, $options = null, $default = null) {
 698          if (is_array($options) && array_key_exists($key, $options)) {
 699              $value= $options[$key];
 700          } elseif (array_key_exists($key, $this->_options)) {
 701              $value= $this->_options[$key];
 702          } else {
 703              $value= $this->xpdo->getOption($key, null, $default);
 704          }
 705          return $value;
 706      }
 707  
 708      /**
 709       * Set an option value for this instance.
 710       *
 711       * @param string $key The option key to set a value for.
 712       * @param mixed $value A value to assign to the option.
 713       */
 714      public function setOption($key, $value) {
 715          $this->_options[$key]= $value;
 716      }
 717  
 718      public function __get($name) {
 719          if ($this->getOption(xPDO::OPT_HYDRATE_FIELDS) && array_key_exists($name, $this->_fields)) {
 720              return $this->_fields[$name];
 721          } elseif ($this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS)) {
 722              if (array_key_exists($name, $this->_composites)) {
 723                  $fkMeta = $this->_composites[$name];
 724              } elseif (array_key_exists($name, $this->_aggregates)) {
 725                  $fkMeta = $this->_aggregates[$name];
 726              } else {
 727                  return null;
 728              }
 729          } else {
 730              return null;
 731          }
 732          if ($fkMeta['cardinality'] === 'many') {
 733              return $this->getMany($name);
 734          } else {
 735              return $this->getOne($name);
 736          }
 737      }
 738  
 739      public function __set($name, $value) {
 740          if ($this->getOption(xPDO::OPT_HYDRATE_FIELDS) && array_key_exists($name, $this->_fields)) {
 741              return $this->_setRaw($name, $value);
 742          } elseif ($this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS)) {
 743              if (array_key_exists($name, $this->_composites)) {
 744                  $fkMeta = $this->_composites[$name];
 745              } elseif (array_key_exists($name, $this->_aggregates)) {
 746                  $fkMeta = $this->_aggregates[$name];
 747              } else {
 748                  return false;
 749              }
 750          } else {
 751              return false;
 752          }
 753          if ($fkMeta['cardinality'] === 'many') {
 754              return $this->addMany($value, $name);
 755          } else {
 756              return $this->addOne($value, $name);
 757          }
 758      }
 759  
 760      public function __isset($name) {
 761          return ($this->getOption(xPDO::OPT_HYDRATE_FIELDS) && array_key_exists($name, $this->_fields) && isset($this->_fields[$name]))
 762              || ($this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS)
 763                  && ((array_key_exists($name, $this->_composites) && isset($this->_composites[$name]))
 764                  || (array_key_exists($name, $this->_aggregates) && isset($this->_aggregates[$name]))));
 765      }
 766  
 767      /**
 768       * Set a field value by the field key or name.
 769       *
 770       * @todo Define and implement field validation.
 771       *
 772       * @param string $k The field key or name.
 773       * @param mixed $v The value to set the field to.
 774       * @param string|callable $vType A string indicating the format of the
 775       * provided value parameter, or a callable function that should be used to
 776       * set the field value, overriding the default behavior.
 777       * @return boolean Determines whether the value was set successfully and was
 778       * determined to be dirty (i.e. different from the previous value).
 779       */
 780      public function set($k, $v= null, $vType= '') {
 781          $set= false;
 782          $callback= '';
 783          $callable= !empty($vType) && is_callable($vType, false, $callback) ? true : false;
 784          $oldValue= null;
 785          $k = $this->getField($k);
 786          if (is_string($k) && !empty($k)) {
 787              if (array_key_exists($k, $this->_fieldMeta)) {
 788                  $oldValue= $this->_fields[$k];
 789                  if (isset ($this->_fieldMeta[$k]['index']) && $this->_fieldMeta[$k]['index'] === 'pk' && isset ($this->_fieldMeta[$k]['generated'])) {
 790                      if (!$this->_fieldMeta[$k]['generated'] === 'callback') {
 791                          return false;
 792                      }
 793                  }
 794                  if ($callable && $callback) {
 795                      $set = $callback($k, $v, $this);
 796                  } else {
 797                      if (is_string($v) && $this->getOption(xPDO::OPT_ON_SET_STRIPSLASHES)) {
 798                          $v= stripslashes($v);
 799                      }
 800                      if ($oldValue !== $v) {
 801                          //type validation
 802                          $phptype= $this->_fieldMeta[$k]['phptype'];
 803                          $dbtype= $this->_fieldMeta[$k]['dbtype'];
 804                          $allowNull= isset($this->_fieldMeta[$k]['null']) ? (boolean) $this->_fieldMeta[$k]['null'] : true;
 805                          if ($v === null) {
 806                              if ($allowNull) {
 807                                  $this->_fields[$k]= null;
 808                                  $set= true;
 809                              } else {
 810                                  $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "{$this->_class}: Attempt to set NOT NULL field {$k} to NULL");
 811                              }
 812                          }
 813                          else {
 814                              switch ($phptype) {
 815                                  case 'timestamp' :
 816                                  case 'datetime' :
 817                                      $ts= false;
 818                                      if (preg_match('/int/i', $dbtype)) {
 819                                          if (strtolower($vType) == 'integer' || is_int($v) || $v == '0') {
 820                                              $ts= (integer) $v;
 821                                          } else {
 822                                              $ts= strtotime($v);
 823                                          }
 824                                          if ($ts === false) {
 825                                              $ts= 0;
 826                                          }
 827                                          $this->_fields[$k]= $ts;
 828                                          $set= true;
 829                                      } else {
 830                                          if ($vType == 'utc' || in_array($v, $this->xpdo->driver->_currentTimestamps) || $v === '0000-00-00 00:00:00') {
 831                                              $this->_fields[$k]= (string) $v;
 832                                              $set= true;
 833                                          } else {
 834                                              if (strtolower($vType) == 'integer' || is_int($v)) {
 835                                                  $ts= intval($v);
 836                                              } elseif (is_string($v) && !empty($v)) {
 837                                                  $ts= strtotime($v);
 838                                              }
 839                                              if ($ts !== false) {
 840                                                  $this->_fields[$k]= strftime('%Y-%m-%d %H:%M:%S', $ts);
 841                                                  $set= true;
 842                                              }
 843                                          }
 844                                      }
 845                                      break;
 846                                  case 'date' :
 847                                      if (preg_match('/int/i', $dbtype)) {
 848                                          if (strtolower($vType) == 'integer' || is_int($v) || $v == '0') {
 849                                              $ts= (integer) $v;
 850                                          } else {
 851                                              $ts= strtotime($v);
 852                                          }
 853                                          if ($ts === false) {
 854                                              $ts= 0;
 855                                          }
 856                                          $this->_fields[$k]= $ts;
 857                                          $set= true;
 858                                      } else {
 859                                          if ($vType == 'utc' || in_array($v, $this->xpdo->driver->_currentDates) || $v === '0000-00-00') {
 860                                              $this->_fields[$k]= $v;
 861                                              $set= true;
 862                                          } else {
 863                                              if (strtolower($vType) == 'integer' || is_int($v)) {
 864                                                  $ts= intval($v);
 865                                              } elseif (is_string($v) && !empty($v)) {
 866                                                  $ts= strtotime($v);
 867                                              }
 868                                              $ts= strtotime($v);
 869                                              if ($ts !== false) {
 870                                                  $this->_fields[$k]= strftime('%Y-%m-%d', $ts);
 871                                                  $set= true;
 872                                              }
 873                                          }
 874                                      }
 875                                      break;
 876                                  case 'boolean' :
 877                                      $this->_fields[$k]= intval($v);
 878                                      $set= true;
 879                                      break;
 880                                  case 'integer' :
 881                                      $this->_fields[$k]= intval($v);
 882                                      $set= true;
 883                                      break;
 884                                  case 'array' :
 885                                      if (is_object($v) && $v instanceof xPDOObject) {
 886                                          $v = $v->toArray();
 887                                      }
 888                                      if (is_array($v)) {
 889                                          $this->_fields[$k]= serialize($v);
 890                                          $set= true;
 891                                      }
 892                                      break;
 893                                  case 'json' :
 894                                      if (is_object($v) && $v instanceof xPDOObject) {
 895                                          $v = $v->toArray();
 896                                      }
 897                                      if (is_string($v)) {
 898                                          $v= $this->xpdo->fromJSON($v, true);
 899                                      }
 900                                      if (is_array($v)) {
 901                                          $this->_fields[$k]= $this->xpdo->toJSON($v);
 902                                          $set= true;
 903                                      }
 904                                      break;
 905                                  default :
 906                                      $this->_fields[$k]= $v;
 907                                      $set= true;
 908                              }
 909                          }
 910                      }
 911                  }
 912              } elseif ($this->getOption(xPDO::OPT_HYDRATE_ADHOC_FIELDS)) {
 913                  $oldValue= isset($this->_fields[$k]) ? $this->_fields[$k] : null;
 914                  if ($callable) {
 915                      $set = $callback($k, $v, $this);
 916                  } else {
 917                      $this->_fields[$k]= $v;
 918                      $set= true;
 919                  }
 920              }
 921              if ($set && $oldValue !== $this->_fields[$k]) {
 922                  $this->setDirty($k);
 923              } else {
 924                  $set= false;
 925              }
 926          } else {
 927              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'xPDOObject - Called set() with an invalid field name: ' . print_r($k, 1));
 928          }
 929          return $set;
 930      }
 931  
 932      /**
 933       * Get a field value (or a set of values) by the field key(s) or name(s).
 934       *
 935       * Warning: do not use the $format parameter if retrieving multiple values of
 936       * different types, as the format string will be applied to all types, most
 937       * likely with unpredictable results.  Optionally, you can supply an associate
 938       * array of format strings with the field key as the key for the format array.
 939       *
 940       * @param string|array $k A string (or an array of strings) representing the field
 941       * key or name.
 942       * @param string|array $format An optional variable (or an array of variables) to
 943       * format the return value(s).
 944       * @param mixed $formatTemplate An additional optional variable that can be used in
 945       * formatting the return value(s).
 946       * @return mixed The value(s) of the field(s) requested.
 947       */
 948      public function get($k, $format = null, $formatTemplate= null) {
 949          $value= null;
 950          if (is_array($k)) {
 951              $lazy = array_intersect($k, $this->_lazy);
 952              if ($lazy) {
 953                  $this->_loadFieldData($lazy);
 954              }
 955              foreach ($k as $key) {
 956                  if (array_key_exists($key, $this->_fields)) {
 957                      if (is_array($format) && isset ($format[$key])) {
 958                          $formatTpl= null;
 959                          if (is_array ($formatTemplate) && isset ($formatTemplate[$key])) {
 960                              $formatTpl= $formatTemplate[$key];
 961                          }
 962                          $value[$key]= $this->get($key, $format[$key], $formatTpl);
 963                      } elseif (!empty ($format) && is_string($format)) {
 964                          $value[$key]= $this->get($key, $format, $formatTemplate);
 965                      } else {
 966                          $value[$key]= $this->get($key);
 967                      }
 968                  }
 969              }
 970          } elseif (is_string($k) && !empty($k)) {
 971              if (array_key_exists($k, $this->_fields)) {
 972                  if ($this->isLazy($k)) {
 973                      $this->_loadFieldData($k);
 974                  }
 975                  $dbType= $this->_getDataType($k);
 976                  $fieldType= $this->_getPHPType($k);
 977                  $value= $this->_fields[$k];
 978                  if ($value !== null) {
 979                      switch ($fieldType) {
 980                          case 'boolean' :
 981                              $value= (boolean) $value;
 982                              break;
 983                          case 'integer' :
 984                              $value= intval($value);
 985                              if (is_string($format) && !empty ($format)) {
 986                                  if (strpos($format, 're:') === 0) {
 987                                      if (!empty ($formatTemplate) && is_string($formatTemplate)) {
 988                                          $value= preg_replace(substr($format, 3), $formatTemplate, $value);
 989                                      }
 990                                  } else {
 991                                      $value= sprintf($format, $value);
 992                                  }
 993                              }
 994                              break;
 995                          case 'float' :
 996                              $value= (float) $value;
 997                              if (is_string($format) && !empty ($format)) {
 998                                  if (strpos($format, 're:') === 0) {
 999                                      if (!empty ($formatTemplate) && is_string($formatTemplate)) {
1000                                          $value= preg_replace(substr($format, 3), $formatTemplate, $value);
1001                                      }
1002                                  } else {
1003                                      $value= sprintf($format, $value);
1004                                  }
1005                              }
1006                              break;
1007                          case 'timestamp' :
1008                          case 'datetime' :
1009                              if (preg_match('/int/i', $dbType)) {
1010                                  $ts= intval($value);
1011                              } elseif (in_array($value, $this->xpdo->driver->_currentTimestamps)) {
1012                                  $ts= time();
1013                              } else {
1014                                  $ts= strtotime($value);
1015                              }
1016                              if ($ts !== false && !empty($value)) {
1017                                  if (is_string($format) && !empty ($format)) {
1018                                      if (strpos($format, 're:') === 0) {
1019                                          $value= date('Y-m-d H:M:S', $ts);
1020                                          if (!empty ($formatTemplate) && is_string($formatTemplate)) {
1021                                              $value= preg_replace(substr($format, 3), $formatTemplate, $value);
1022                                          }
1023                                      } elseif (strpos($format, '%') === false) {
1024                                          $value= date($format, $ts);
1025                                      } else {
1026                                          $value= strftime($format, $ts);
1027                                      }
1028                                  } else {
1029                                      $value= strftime('%Y-%m-%d %H:%M:%S', $ts);
1030                                  }
1031                              }
1032                              break;
1033                          case 'date' :
1034                              if (preg_match('/int/i', $dbType)) {
1035                                  $ts= intval($value);
1036                              } elseif (in_array($value, $this->xpdo->driver->_currentDates)) {
1037                                  $ts= time();
1038                              } else {
1039                                  $ts= strtotime($value);
1040                              }
1041                              if ($ts !== false && !empty($value)) {
1042                                  if (is_string($format) && !empty ($format)) {
1043                                      if (strpos($format, 're:') === 0) {
1044                                          $value= strftime('%Y-%m-%d', $ts);
1045                                          if (!empty ($formatTemplate) && is_string($formatTemplate)) {
1046                                              $value= preg_replace(substr($format, 3), $formatTemplate, $value);
1047                                          }
1048                                      } elseif (strpos($format, '%') === false) {
1049                                          $value= date($format, $ts);
1050                                      } elseif ($ts !== false) {
1051                                          $value= strftime($format, $ts);
1052                                      }
1053                                  } else {
1054                                      $value= strftime('%Y-%m-%d', $ts);
1055                                  }
1056                              }
1057                              break;
1058                          case 'array' :
1059                              if (is_string($value)) {
1060                                  $value= unserialize($value);
1061                              }
1062                              break;
1063                          case 'json' :
1064                              if (is_string($value) && strlen($value) > 1) {
1065                                  $value= $this->xpdo->fromJSON($value, true);
1066                              }
1067                              break;
1068                          default :
1069                              if (is_string($format) && !empty ($format)) {
1070                                  if (strpos($format, 're:') === 0) {
1071                                      if (!empty ($formatTemplate) && is_string($formatTemplate)) {
1072                                          $value= preg_replace(substr($format, 3), $formatTemplate, $value);
1073                                      }
1074                                  } else {
1075                                      $value= sprintf($format, $value);
1076                                  }
1077                              }
1078                              break;
1079                      }
1080                  }
1081              }
1082          }
1083          return $value;
1084      }
1085  
1086      /**
1087       * Gets an object related to this instance by a foreign key relationship.
1088       *
1089       * Use this for 1:? (one:zero-or-one) or 1:1 relationships, which you can
1090       * distinguish by setting the nullability of the field representing the
1091       * foreign key.
1092       *
1093       * For all 1:* relationships for this instance, see {@link getMany()}.
1094       *
1095       * @see xPDOObject::getMany()
1096       * @see xPDOObject::addOne()
1097       * @see xPDOObject::addMany()
1098       *
1099       * @param string $alias Alias of the foreign class representing the related
1100       * object.
1101       * @param object $criteria xPDOCriteria object to get the related objects
1102       * @param boolean|integer $cacheFlag Indicates if the object should be
1103       * cached and optionally, by specifying an integer value, for how many
1104       * seconds.
1105       * @return xPDOObject|null The related object or null if no instance exists.
1106       */
1107      public function & getOne($alias, $criteria= null, $cacheFlag= true) {
1108          $object= null;
1109          if ($fkdef= $this->getFKDefinition($alias)) {
1110              $k= $fkdef['local'];
1111              $fk= $fkdef['foreign'];
1112              if (isset ($this->_relatedObjects[$alias])) {
1113                  if (is_object($this->_relatedObjects[$alias])) {
1114                      $object= & $this->_relatedObjects[$alias];
1115                      return $object;
1116                  }
1117              }
1118              if ($criteria === null) {
1119                  $criteria= array ($fk => $this->get($k));
1120                  if (isset($fkdef['criteria']) && isset($fkdef['criteria']['foreign'])) {
1121                      $criteria= array($fkdef['criteria']['foreign'], $criteria);
1122                  }
1123              }
1124              if ($object= $this->xpdo->getObject($fkdef['class'], $criteria, $cacheFlag)) {
1125                  $this->_relatedObjects[$alias]= $object;
1126              }
1127          } else {
1128              $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Could not getOne: foreign key definition for alias {$alias} not found.");
1129          }
1130          return $object;
1131      }
1132  
1133      /**
1134       * Gets a collection of objects related by aggregate or composite relations.
1135       *
1136       * @see xPDOObject::getOne()
1137       * @see xPDOObject::addOne()
1138       * @see xPDOObject::addMany()
1139       *
1140       * @param string $alias Alias of the foreign class representing the related
1141       * object.
1142       * @param object $criteria xPDOCriteria object to get the related objects
1143       * @param boolean|integer $cacheFlag Indicates if the objects should be
1144       * cached and optionally, by specifying an integer value, for how many
1145       * seconds.
1146       * @return array A collection of related objects or an empty array.
1147       */
1148      public function & getMany($alias, $criteria= null, $cacheFlag= true) {
1149          $collection= $this->_getRelatedObjectsByFK($alias, $criteria, $cacheFlag);
1150          return $collection;
1151      }
1152  
1153      /**
1154       * Get an xPDOIterator for a collection of objects related by aggregate or composite relations.
1155       *
1156       * @param string $alias The alias of the relation.
1157       * @param null|array|xPDOCriteria $criteria A valid xPDO criteria expression.
1158       * @param bool|int $cacheFlag Indicates if the objects should be cached and optionally, by
1159       * specifying  an integer values, for how many seconds.
1160       * @return bool|xPDOIterator An iterator for the collection or false if no relation is found.
1161       */
1162      public function getIterator($alias, $criteria= null, $cacheFlag= true) {
1163          $iterator = false;
1164          $fkMeta= $this->getFKDefinition($alias);
1165          if ($fkMeta) {
1166              $fkCriteria = isset($fkMeta['criteria']) && isset($fkMeta['criteria']['foreign']) ? $fkMeta['criteria']['foreign'] : null;
1167              if ($criteria === null) {
1168                  $criteria= array($fkMeta['foreign'] => $this->get($fkMeta['local']));
1169                  if ($fkCriteria !== null) {
1170                      $criteria = array($fkCriteria, $criteria);
1171                  }
1172              } else {
1173                  $criteria= $this->xpdo->newQuery($fkMeta['class'], $criteria);
1174                  $addCriteria = array("{$criteria->getAlias()}.{$fkMeta['foreign']}" => $this->get($fkMeta['local']));
1175                  if ($fkCriteria !== null) {
1176                      $fkAddCriteria = array();
1177                      foreach ($fkCriteria as $fkCritKey => $fkCritVal) {
1178                          if (is_numeric($fkCritKey)) continue;
1179                          $fkAddCriteria["{$criteria->getAlias()}.{$fkCritKey}"] = $fkCritVal;
1180                      }
1181                      if (!empty($fkAddCriteria)) {
1182                          $addCriteria = array($fkAddCriteria, $addCriteria);
1183                      }
1184                  }
1185                  $criteria->andCondition($addCriteria);
1186              }
1187              $iterator = $this->xpdo->getIterator($fkMeta['class'], $criteria, $cacheFlag);
1188          }
1189          return $iterator;
1190      }
1191  
1192      /**
1193       * Adds an object related to this instance by a foreign key relationship.
1194       *
1195       * @see xPDOObject::getOne()
1196       * @see xPDOObject::getMany()
1197       * @see xPDOObject::addMany()
1198       *
1199       * @param mixed &$obj A single object to be related to this instance.
1200       * @param string $alias The relation alias of the related object (only
1201       * required if more than one relation exists to the same foreign class).
1202       * @return boolean True if the related object was added to this object.
1203       */
1204      public function addOne(& $obj, $alias= '') {
1205          $added= false;
1206          if (is_object($obj)) {
1207              if (empty ($alias)) {
1208                  if ($obj->_alias == $obj->_class) {
1209                      $aliases = $this->_getAliases($obj->_class, 1);
1210                      if (!empty($aliases)) {
1211                          $obj->_alias = reset($aliases);
1212                      }
1213                  }
1214                  $alias= $obj->_alias;
1215              }
1216              $fkMeta= $this->getFKDefinition($alias);
1217              if ($fkMeta && $fkMeta['cardinality'] === 'one') {
1218                  $obj->_alias= $alias;
1219                  $fk= $fkMeta['foreign'];
1220                  $key= $fkMeta['local'];
1221                  $owner= isset ($fkMeta['owner']) ? $fkMeta['owner'] : 'local';
1222                  $kval= $this->get($key);
1223                  $fkval= $obj->get($fk);
1224                  if ($owner == 'local') {
1225                      $fkCriteria = isset($fkMeta['criteria']) && isset($fkMeta['criteria']['foreign']) ? $fkMeta['criteria']['foreign'] : null;
1226                      $obj->set($fk, $kval);
1227                      if (is_array($fkCriteria)) {
1228                          foreach ($fkCriteria as $fkCritKey => $fkCritVal) {
1229                              if (is_numeric($fkCritKey)) continue;
1230                              $obj->set($fkCritKey, $fkCritVal);
1231                          }
1232                      }
1233                  }
1234                  else {
1235                      $this->set($key, $fkval);
1236                      $fkCriteria = isset($fkMeta['criteria']) && isset($fkMeta['criteria']['local']) ? $fkMeta['criteria']['local'] : null;
1237                      if (is_array($fkCriteria)) {
1238                          foreach ($fkCriteria as $fkCritKey => $fkCritVal) {
1239                              if (is_numeric($fkCritKey)) continue;
1240                              $this->set($fkCritKey, $fkCritVal);
1241                          }
1242                      }
1243                  }
1244  
1245                  $this->_relatedObjects[$obj->_alias]= $obj;
1246                  $this->setDirty($key);
1247                  $added= true;
1248              } else {
1249                  $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Foreign key definition for class {$obj->class}, alias {$obj->_alias} not found, or cardinality is not 'one'.");
1250              }
1251          } else {
1252              $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Attempt to add a non-object to a relation with alias ({$alias})");
1253          }
1254          if (!$added) {
1255              $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Could not add related object! " . (is_object($obj) ? print_r($obj->toArray(), true) : ''));
1256          }
1257          return $added;
1258      }
1259  
1260      /**
1261       * Adds an object or collection of objects related to this class.
1262       *
1263       * This method adds an object or collection of objects in a one-to-
1264       * many foreign key relationship with this object to the internal list of
1265       * related objects.  By adding these related objects, you can cascade
1266       * {@link xPDOObject::save()}, {@link xPDOObject::remove()}, and other
1267       * operations based on the type of relationships defined.
1268       *
1269       * @see xPDOObject::addOne()
1270       * @see xPDOObject::getOne()
1271       * @see xPDOObject::getMany()
1272       *
1273       * @param mixed &$obj A single object or collection of objects to be related
1274       * to this instance via the intersection class.
1275       * @param string $alias An optional alias, required only for instances where
1276       * you have more than one relation defined to the same class.
1277       * @return boolean Indicates if the addMany was successful.
1278       */
1279      public function addMany(& $obj, $alias= '') {
1280          $added= false;
1281          if (!is_array($obj)) {
1282              if (is_object($obj)) {
1283                  if (empty ($alias)) {
1284                      if ($obj->_alias == $obj->_class) {
1285                          $aliases = $this->_getAliases($obj->_class, 1);
1286                          if (!empty($aliases)) {
1287                              $obj->_alias = reset($aliases);
1288                          }
1289                      }
1290                      $alias= $obj->_alias;
1291                  }
1292                  if ($fkMeta= $this->getFKDefinition($alias)) {
1293                      $obj->_alias= $alias;
1294                      if ($fkMeta['cardinality'] === 'many') {
1295                          if ($obj->_new) {
1296                              $objpk= '__new' . (isset ($this->_relatedObjects[$alias]) ? count($this->_relatedObjects[$alias]) : 0);
1297                          } else {
1298                              $objpk= $obj->getPrimaryKey();
1299                              if (is_array($objpk)) {
1300                                  $objpk= implode('-', $objpk);
1301                              }
1302                          }
1303                          $this->_relatedObjects[$alias][$objpk]= $obj;
1304                          if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, 'Added related object with alias: ' . $alias . ' and pk: ' . $objpk . "\n" . print_r($obj->toArray('', true), true));
1305                          $added= true;
1306                      }
1307                  }
1308              }
1309          } else {
1310              foreach ($obj as $o) {
1311                  $added= $this->addMany($o, $alias);
1312              }
1313          }
1314          return $added;
1315      }
1316  
1317      /**
1318       * Persist new or changed objects to the database container.
1319       *
1320       * Inserts or updates the database record representing this object and any
1321       * new or changed related object records.  Both aggregate and composite
1322       * related objects will be saved as appropriate, before or following the
1323       * save operation on the controlling instance.
1324       *
1325       * @param boolean|integer $cacheFlag Indicates if the saved object(s) should
1326       * be cached and optionally, by specifying an integer value, for how many
1327       * seconds before expiring.  Overrides the cacheFlag for the object(s).
1328       * @return boolean Returns true on success, false on failure.
1329       */
1330      public function save($cacheFlag= null) {
1331          if ($this->isLazy()) {
1332              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Attempt to save lazy object: ' . print_r($this->toArray('', true), 1));
1333              return false;
1334          }
1335          $result= true;
1336          $sql= '';
1337          $pk= $this->getPrimaryKey();
1338          $pkn= $this->getPK();
1339          $pkGenerated= false;
1340          if ($this->isNew()) {
1341              $this->setDirty();
1342          }
1343          if ($this->getOption(xPDO::OPT_VALIDATE_ON_SAVE)) {
1344              if (!$this->validate()) {
1345                  return false;
1346              }
1347          }
1348          if (!$this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
1349              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not get connection for writing data", '', __METHOD__, __FILE__, __LINE__);
1350              return false;
1351          }
1352          $this->_saveRelatedObjects();
1353          if (!empty ($this->_dirty)) {
1354              $cols= array ();
1355              $bindings= array ();
1356              $updateSql= array ();
1357              foreach (array_keys($this->_dirty) as $_k) {
1358                  if (!array_key_exists($_k, $this->_fieldMeta)) {
1359                      continue;
1360                  }
1361                  if (isset($this->_fieldMeta[$_k]['generated'])) {
1362                      if (!$this->_new || !isset($this->_fields[$_k]) || empty($this->_fields[$_k])) {
1363                          $pkGenerated= true;
1364                          continue;
1365                      }
1366                  }
1367                  if ($this->_fieldMeta[$_k]['phptype'] === 'password') {
1368                      $this->_fields[$_k]= $this->encode($this->_fields[$_k], 'password');
1369                  }
1370                  $fieldType= PDO::PARAM_STR;
1371                  $fieldValue= $this->_fields[$_k];
1372                  if (in_array($this->_fieldMeta[$_k]['phptype'], array ('datetime', 'timestamp')) && !empty($this->_fieldMeta[$_k]['attributes']) && $this->_fieldMeta[$_k]['attributes'] == 'ON UPDATE CURRENT_TIMESTAMP') {
1373                      $this->_fields[$_k]= strftime('%Y-%m-%d %H:%M:%S');
1374                      continue;
1375                  }
1376                  elseif ($fieldValue === null || $fieldValue === 'NULL') {
1377                      if ($this->_new) continue;
1378                      $fieldType= PDO::PARAM_NULL;
1379                      $fieldValue= null;
1380                  }
1381                  elseif (in_array($this->_fieldMeta[$_k]['phptype'], array ('timestamp', 'datetime')) && in_array($fieldValue, $this->xpdo->driver->_currentTimestamps, true)) {
1382                      $this->_fields[$_k]= strftime('%Y-%m-%d %H:%M:%S');
1383                      continue;
1384                  }
1385                  elseif (in_array($this->_fieldMeta[$_k]['phptype'], array ('date')) && in_array($fieldValue, $this->xpdo->driver->_currentDates, true)) {
1386                      $this->_fields[$_k]= strftime('%Y-%m-%d');
1387                      continue;
1388                  }
1389                  elseif ($this->_fieldMeta[$_k]['phptype'] == 'timestamp' && preg_match('/int/i', $this->_fieldMeta[$_k]['dbtype'])) {
1390                      $fieldType= PDO::PARAM_INT;
1391                  }
1392                  elseif (!in_array($this->_fieldMeta[$_k]['phptype'], array ('string','password','datetime','timestamp','date','time','array','json','float'))) {
1393                      $fieldType= PDO::PARAM_INT;
1394                  }
1395                  if ($this->_new) {
1396                      $cols[$_k]= $this->xpdo->escape($_k);
1397                      $bindings[":{$_k}"]['value']= $fieldValue;
1398                      $bindings[":{$_k}"]['type']= $fieldType;
1399                  } else {
1400                      $bindings[":{$_k}"]['value']= $fieldValue;
1401                      $bindings[":{$_k}"]['type']= $fieldType;
1402                      $updateSql[]= $this->xpdo->escape($_k) . " = :{$_k}";
1403                  }
1404              }
1405              if ($this->_new) {
1406                  $sql= "INSERT INTO {$this->_table} (" . implode(', ', array_values($cols)) . ") VALUES (" . implode(', ', array_keys($bindings)) . ")";
1407              } else {
1408                  if ($pk && $pkn) {
1409                      if (is_array($pkn)) {
1410                          $iteration= 0;
1411                          $where= '';
1412                          foreach ($pkn as $k => $v) {
1413                              $vt= PDO::PARAM_INT;
1414                              if (in_array($this->_fieldMeta[$k]['phptype'], array('string', 'float'))) {
1415                                  $vt= PDO::PARAM_STR;
1416                              }
1417                              if ($iteration) {
1418                                  $where .= " AND ";
1419                              }
1420                              $where .= $this->xpdo->escape($k) . " = :{$k}";
1421                              $bindings[":{$k}"]['value']= $this->_fields[$k];
1422                              $bindings[":{$k}"]['type']= $vt;
1423                              $iteration++;
1424                          }
1425                      } else {
1426                          $pkn= $this->getPK();
1427                          $pkt= PDO::PARAM_INT;
1428                          if (in_array($this->_fieldMeta[$pkn]['phptype'], array('string', 'float'))) {
1429                              $pkt= PDO::PARAM_STR;
1430                          }
1431                          $bindings[":{$pkn}"]['value']= $pk;
1432                          $bindings[":{$pkn}"]['type']= $pkt;
1433                          $where= $this->xpdo->escape($pkn) . ' = :' . $pkn;
1434                      }
1435                      if (!empty ($updateSql)) {
1436                          $sql= "UPDATE {$this->_table} SET " . implode(',', $updateSql) . " WHERE {$where}";
1437                      }
1438                  }
1439              }
1440              if (!empty ($sql) && $criteria= new xPDOCriteria($this->xpdo, $sql)) {
1441                  if ($criteria->prepare()) {
1442                      if (!empty ($bindings)) {
1443                          $criteria->bind($bindings, true, false);
1444                      }
1445                      if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Executing SQL:\n{$sql}\nwith bindings:\n" . print_r($bindings, true));
1446                      $tstart = microtime(true);
1447                      if (!$result= $criteria->stmt->execute()) {
1448                          $this->xpdo->queryTime += microtime(true) - $tstart;
1449                          $this->xpdo->executedQueries++;
1450                          $errorInfo= $criteria->stmt->errorInfo();
1451                          $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement:\n" . $criteria->toSQL() . "\n" . print_r($errorInfo, true));
1452                          if (($errorInfo[1] == '1146' || $errorInfo[1] == '1') && $this->getOption(xPDO::OPT_AUTO_CREATE_TABLES)) {
1453                              if ($this->xpdo->getManager() && $this->xpdo->manager->createObjectContainer($this->_class) === true) {
1454                                  $tstart = microtime(true);
1455                                  if (!$result= $criteria->stmt->execute()) {
1456                                      $this->xpdo->queryTime += microtime(true) - $tstart;
1457                                      $this->xpdo->executedQueries++;
1458                                      $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement:\n{$sql}\n");
1459                                  } else {
1460                                      $this->xpdo->queryTime += microtime(true) - $tstart;
1461                                      $this->xpdo->executedQueries++;
1462                                  }
1463                              } else {
1464                                  $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $this->xpdo->errorCode() . " attempting to create object container for class {$this->_class}:\n" . print_r($this->xpdo->errorInfo(), true));
1465                              }
1466                          }
1467                      } else {
1468                          $this->xpdo->queryTime += microtime(true) - $tstart;
1469                          $this->xpdo->executedQueries++;
1470                      }
1471                  } else {
1472                      $result= false;
1473                  }
1474                  if ($result) {
1475                      if ($pkn && !$pk) {
1476                          if ($pkGenerated) {
1477                              $this->_fields[$this->getPK()]= $this->xpdo->lastInsertId();
1478                          }
1479                          $pk= $this->getPrimaryKey();
1480                      }
1481                      if ($pk || !$this->getPK()) {
1482                          $this->_dirty= array();
1483                          $this->_validated= array();
1484                          $this->_new= false;
1485                      }
1486                      $callback = $this->getOption(xPDO::OPT_CALLBACK_ON_SAVE);
1487                      if ($callback && is_callable($callback)) {
1488                          call_user_func($callback, array('className' => $this->_class, 'criteria' => $criteria, 'object' => $this));
1489                      }
1490                      if ($this->xpdo->_cacheEnabled && $pk && ($cacheFlag || ($cacheFlag === null && $this->_cacheFlag))) {
1491                          $cacheKey= $this->xpdo->newQuery($this->_class, $pk, $cacheFlag);
1492                          if (is_bool($cacheFlag)) {
1493                              $expires= 0;
1494                          } else {
1495                              $expires= intval($cacheFlag);
1496                          }
1497                          $this->xpdo->toCache($cacheKey, $this, $expires, array('modified' => true));
1498                      }
1499                  }
1500              }
1501          }
1502          $this->_saveRelatedObjects();
1503          if ($result) {
1504              $this->_dirty= array ();
1505              $this->_validated= array ();
1506          }
1507          return $result;
1508      }
1509  
1510      /**
1511       * Searches for any related objects with pending changes to save.
1512       *
1513       * @access protected
1514       * @uses xPDOObject::_saveRelatedObject()
1515       * @return integer The number of related objects processed.
1516       */
1517      protected function _saveRelatedObjects() {
1518          $saved= 0;
1519          if (!empty ($this->_relatedObjects)) {
1520              foreach ($this->_relatedObjects as $alias => $ro) {
1521                  $objects= array ();
1522                  if (is_object($ro)) {
1523                      $primaryKey= $ro->_new ? '__new' : $ro->getPrimaryKey();
1524                      if (is_array($primaryKey)) $primaryKey= implode('-', $primaryKey);
1525                      $objects[$primaryKey]= & $ro;
1526                      $cardinality= 'one';
1527                  }
1528                  elseif (is_array($ro)) {
1529                      $objects= $ro;
1530                      $cardinality= 'many';
1531                  }
1532                  if (!empty($objects)) {
1533                      foreach ($objects as $pk => $obj) {
1534                          if ($fkMeta= $this->getFKDefinition($alias)) {
1535                              if ($this->_saveRelatedObject($obj, $fkMeta)) {
1536                                  if ($cardinality == 'many') {
1537                                      $newPk= $obj->getPrimaryKey();
1538                                      if (is_array($newPk)) $newPk= implode('-', $newPk);
1539                                      if ($pk != $newPk) {
1540                                          $this->_relatedObjects[$alias][$newPk]= $obj;
1541                                          unset($this->_relatedObjects[$alias][$pk]);
1542                                      }
1543                                  }
1544                                  $saved++;
1545                              }
1546                          }
1547                      }
1548                  }
1549              }
1550          }
1551          return $saved;
1552      }
1553  
1554      /**
1555       * Save a related object with pending changes.
1556       *
1557       * This function is also responsible for setting foreign keys when new
1558       * related objects are being saved, as well as local keys when the host
1559       * object is new and needs the foreign key.
1560       *
1561       * @access protected
1562       * @param xPDOObject &$obj A reference to the related object.
1563       * @param array $fkMeta The meta data representing the relation.
1564       * @return boolean True if a related object was dirty and saved successfully.
1565       */
1566      protected function _saveRelatedObject(& $obj, $fkMeta) {
1567          $saved= false;
1568          $local= $fkMeta['local'];
1569          $foreign= $fkMeta['foreign'];
1570          $cardinality= $fkMeta['cardinality'];
1571          $owner= isset ($fkMeta['owner']) ? $fkMeta['owner'] : '';
1572          if (!$owner) {
1573              $owner= $cardinality === 'many' ? 'foreign' : 'local';
1574          }
1575          $criteria = isset($fkMeta['criteria']) ? $fkMeta['criteria'] : null;
1576          if ($owner === 'local' && $fk= $this->get($local)) {
1577              $obj->set($foreign, $fk);
1578              if (isset($criteria['foreign']) && is_array($criteria['foreign'])) {
1579                  foreach ($criteria['foreign'] as $critKey => $critVal) {
1580                      if (is_numeric($critKey)) continue;
1581                      $obj->set($critKey, $critVal);
1582                  }
1583              }
1584              $saved= $obj->save();
1585          } elseif ($owner === 'foreign') {
1586              if ($obj->isNew() || !empty($obj->_dirty)) {
1587                  $saved= $obj->save();
1588              }
1589              $fk= $obj->get($foreign);
1590              if ($fk) {
1591                  $this->set($local, $fk);
1592                  if (isset($criteria['local']) && is_array($criteria['local'])) {
1593                      foreach ($criteria['local'] as $critKey => $critVal) {
1594                          if (is_numeric($critKey)) continue;
1595                          $this->set($critKey, $critVal);
1596                      }
1597                  }
1598              }
1599          }
1600          if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG , ($saved ? 'Successfully saved' : 'Could not save') . " related object\nMain object: " . print_r($this->toArray('', true), true) . "\nRelated Object: " . print_r($obj->toArray('', true), true));
1601          return $saved;
1602      }
1603  
1604      /**
1605       * Remove the persistent instance of an object permanently.
1606       *
1607       * Deletes the persistent object instance stored in the database when
1608       * called, including any dependent objects defined by composite foreign key
1609       * relationships.
1610       *
1611       * @todo Implement some way to reassign ownership of related composite
1612       * objects when remove is called, perhaps by passing another object
1613       * instance as an optional parameter, or creating a separate method.
1614       *
1615       * @param array $ancestors Keeps track of classes which have already been
1616       * removed to prevent loop with circular references.
1617       * @return boolean Returns true on success, false on failure.
1618       */
1619      public function remove(array $ancestors= array ()) {
1620          $result= false;
1621          $pk= $this->getPrimaryKey();
1622          if ($pk && $this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
1623              if (!empty ($this->_composites)) {
1624                  $current= array ($this->_class, $this->_alias);
1625                  foreach ($this->_composites as $compositeAlias => $composite) {
1626                      if (in_array($compositeAlias, $ancestors) || in_array($composite['class'], $ancestors)) {
1627                          continue;
1628                      }
1629                      if ($composite['cardinality'] === 'many') {
1630                          if ($many= $this->getMany($compositeAlias)) {
1631                              /** @var xPDOObject $one */
1632                              foreach ($many as $one) {
1633                                  $ancestors[]= $compositeAlias;
1634                                  $newAncestors= $ancestors + $current;
1635                                  if (!$one->remove($newAncestors)) {
1636                                      $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error removing dependent object: " . print_r($one->toArray('', true), true));
1637                                  }
1638                              }
1639                              unset($many);
1640                          }
1641                      }
1642                      elseif ($one= $this->getOne($compositeAlias)) {
1643                          $ancestors[]= $compositeAlias;
1644                          $newAncestors= $ancestors + $current;
1645                          if (!$one->remove($newAncestors)) {
1646                              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error removing dependent object: " . print_r($one->toArray('', true), true));
1647                          }
1648                          unset($one);
1649                      }
1650                  }
1651              }
1652              $delete= $this->xpdo->newQuery($this->_class);
1653              $delete->command('DELETE');
1654              $delete->where($pk);
1655              // $delete->limit(1);
1656              $stmt= $delete->prepare();
1657              if (is_object($stmt)) {
1658                  $tstart = microtime(true);
1659                  if (!$result= $stmt->execute()) {
1660                      $this->xpdo->queryTime += microtime(true) - $tstart;
1661                      $this->xpdo->executedQueries++;
1662                      $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not delete from ' . $this->_table . '; primary key specified was ' . print_r($pk, true) . "\n" . print_r($stmt->errorInfo(), true));
1663                  } else {
1664                      $this->xpdo->queryTime += microtime(true) - $tstart;
1665                      $this->xpdo->executedQueries++;
1666                      $callback = $this->getOption(xPDO::OPT_CALLBACK_ON_REMOVE);
1667                      if ($callback && is_callable($callback)) {
1668                          call_user_func($callback, array('className' => $this->_class, 'criteria' => $delete, 'object' => $this));
1669                      }
1670                      if ($this->xpdo->_cacheEnabled && $this->xpdo->getOption('cache_db', null, false)) {
1671                          /** @var xPDOCache $dbCache */
1672                          $dbCache = $this->xpdo->getCacheManager()->getCacheProvider(
1673                              $this->getOption('cache_db_key', null, 'db'),
1674                              array(
1675                                  xPDO::OPT_CACHE_KEY => $this->getOption('cache_db_key', null, 'db'),
1676                                  xPDO::OPT_CACHE_HANDLER => $this->getOption(xPDO::OPT_CACHE_DB_HANDLER, null, $this->getOption(xPDO::OPT_CACHE_HANDLER, null, 'cache.xPDOFileCache')),
1677                                  xPDO::OPT_CACHE_FORMAT => (integer) $this->getOption('cache_db_format', null, $this->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)),
1678                                  xPDO::OPT_CACHE_EXPIRES => (integer) $this->getOption(xPDO::OPT_CACHE_DB_EXPIRES, null, $this->getOption(xPDO::OPT_CACHE_EXPIRES, null, 0)),
1679                                  xPDO::OPT_CACHE_PREFIX => $this->getOption('cache_db_prefix', null, xPDOCacheManager::CACHE_DIR)
1680                              )
1681                          );
1682                          if (!$dbCache->delete($this->_class, array('multiple_object_delete' => true))) {
1683                              $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Could not remove cache entries for {$this->_class}", '', __METHOD__, __FILE__, __LINE__);
1684                          }
1685                      }
1686                      $this->xpdo->log(xPDO::LOG_LEVEL_INFO, "Removed {$this->_class} instance with primary key " . print_r($pk, true));
1687                  }
1688              } else {
1689                  $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not build criteria to delete from ' . $this->_table . '; primary key specified was ' . print_r($pk, true));
1690              }
1691          }
1692          return $result;
1693      }
1694  
1695      /**
1696       * Gets the value (or values) of the primary key field(s) for the object.
1697       *
1698       * @param boolean $validateCompound If any of the keys in a compound primary key are empty
1699       * or null, and the default value is not allowed to be null, do not return an array, instead
1700       * return null; the default is true
1701       * @return mixed The string (or an array) representing the value(s) of the
1702       * primary key field(s) for this instance.
1703       */
1704      public function getPrimaryKey($validateCompound= true) {
1705          $value= null;
1706          if ($pk= $this->getPK()) {
1707              if (is_array($pk)) {
1708                  foreach ($pk as $k) {
1709                      $_pk= $this->get($k);
1710                      if (($_pk && strtolower($_pk) !== 'null') || !$validateCompound) {
1711                          $value[]= $_pk;
1712                      } else {
1713                          if (isset ($this->_fieldMeta[$k]['default'])) {
1714                              $value[]= $this->_fieldMeta[$k]['default'];
1715                              $this->_fields[$k]= $this->_fieldMeta[$k]['default'];
1716                          }
1717                          elseif (isset ($this->_fieldMeta[$k]['null']) && $this->_fieldMeta[$k]['null']) {
1718                              $value[]= null;
1719                          }
1720                          else {
1721                              $value= null;
1722                              break;
1723                          }
1724                      }
1725                  }
1726              } else {
1727                  $value= $this->get($pk);
1728              }
1729          }
1730          if (!$value && $this->xpdo->getDebug() === true) {
1731              $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "No primary key value found for pk definition: " . print_r($this->getPK(), true));
1732          }
1733          return $value;
1734      }
1735  
1736      /**
1737       * Gets the name (or names) of the primary key field(s) for the object.
1738       *
1739       * @return mixed The string (or an array of strings) representing the name(s)
1740       * of the primary key field(s) for this instance.
1741       */
1742      public function getPK() {
1743          if ($this->_pk === null) {
1744              $this->_pk= $this->xpdo->getPK($this->_class);
1745          }
1746          return $this->_pk;
1747      }
1748  
1749      /**
1750       * Gets the type of the primary key field for the object.
1751       *
1752       * @return string The type of the primary key field for this instance.
1753       */
1754      public function getPKType() {
1755          if ($this->_pktype === null) {
1756              if ($this->_pk === null) {
1757                  $this->getPK();
1758              }
1759              $this->_pktype= $this->xpdo->getPKType($this->_class);
1760          }
1761          return $this->_pktype;
1762      }
1763  
1764      /**
1765       * Get the name of a class related by foreign key to a specified field key.
1766       *
1767       * This is generally used to lookup classes involved in one-to-one
1768       * relationships with the current object.
1769       *
1770       * @param string $k The field name or key to lookup a related class for.
1771       */
1772      public function getFKClass($k) {
1773          $fkclass= null;
1774          $k = $this->getField($k, true);
1775          if (is_string($k)) {
1776              if (!empty ($this->_aggregates)) {
1777                  foreach ($this->_aggregates as $aggregateAlias => $aggregate) {
1778                      if ($aggregate['local'] === $k) {
1779                          $fkclass= $aggregate['class'];
1780                          break;
1781                      }
1782                  }
1783              }
1784              if (!$fkclass && !empty ($this->_composites)) {
1785                  foreach ($this->_composites as $compositeAlias => $composite) {
1786                      if ($composite['local'] === $k) {
1787                          $fkclass= $composite['class'];
1788                          break;
1789                      }
1790                  }
1791              }
1792              $fkclass= $this->xpdo->loadClass($fkclass);
1793          }
1794          if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Returning foreign key class {$fkclass} for column {$k}");
1795          return $fkclass;
1796      }
1797  
1798      /**
1799       * Get a foreign key definition for a specific classname.
1800       *
1801       * This is generally used to lookup classes in a one-to-many relationship
1802       * with the current object.
1803       *
1804       * @param string $alias Alias of the related class to lookup a foreign key
1805       * definition from.
1806       * @return array A foreign key definition.
1807       */
1808      public function getFKDefinition($alias) {
1809          return $this->xpdo->getFKDefinition($this->_class, $alias);
1810      }
1811  
1812      /**
1813       * Gets a field name as represented in the database container.
1814       *
1815       * This gets the name of the field, fully-qualified by either the object
1816       * table name or a specified alias, and properly quoted.
1817       *
1818       * @param string $k The simple name of the field.
1819       * @param string $alias An optional alias for the table in a specific query.
1820       * @return string The name of the field, qualified with the table name or an
1821       * optional table alias.
1822       */
1823      public function getFieldName($k, $alias= null) {
1824          if ($this->fieldNames === null) {
1825              $this->_initFields();
1826          }
1827          $name= null;
1828          $k = $this->getField($k, true);
1829          if (is_string($k) && isset ($this->fieldNames[$k])) {
1830              $name= $this->fieldNames[$k];
1831          }
1832          if ($name !== null && $alias !== null) {
1833              $name= str_replace("{$this->_table}.", "{$alias}.", $name);
1834          }
1835          return $name;
1836      }
1837  
1838      /**
1839       * Get a field name, looking up any by alias if not an actual field.
1840       *
1841       * @param string $key The field name or alias to translate to the actual field name.
1842       * @param bool $validate If true, the method will return false if the field or an alias
1843       * of it is not found. Otherwise, the key is returned as passed.
1844       * @return string|bool The actual field name, the key as passed, or false if not a field
1845       * or alias and validate is true.
1846       */
1847      public function getField($key, $validate = false) {
1848          $field = $key;
1849          if (!array_key_exists($key, $this->_fieldMeta)) {
1850              if (array_key_exists($key, $this->_fieldAliases)) {
1851                  $field = $this->_fieldAliases[$key];
1852              } elseif ($validate === true) {
1853                  $field = false;
1854              }
1855          }
1856          return $field;
1857      }
1858  
1859      /**
1860       * Load a graph of related objects to the current object.
1861       *
1862       * @param boolean|string|array|integer $graph An option to tell how to deal with related objects. If integer, will
1863       * traverse related objects up to a $graph level of depth and load them to the object.
1864       * If an array, will traverse required related object and load them to the object.
1865       * If true, will traverse the entire graph and append all related objects to the object (default behavior).
1866       * @param xPDOCriteria|array|string|integer $criteria A valid xPDO criteria representation.
1867       * @param boolean|integer $cacheFlag Indicates if the objects should be cached and optionally, by specifying an
1868       * integer value, for how many seconds.
1869       * @return array|boolean The graph that was loaded or false if nothing was loaded.
1870       */
1871      public function getGraph($graph = true, $criteria = null, $cacheFlag = true) {
1872          /* graph is true, get all relations to max depth */
1873          if ($graph === true) {
1874              $graph = $this->xpdo->getGraph($this->_class);
1875          }
1876          /* graph is an int, get relations to depth of graph */
1877          if (is_int($graph)) {
1878              $graph = $this->xpdo->getGraph($this->_class, $graph);
1879          }
1880          /* graph defined as JSON, convert to array */
1881          if (is_string($graph)) {
1882              $graph= $this->xpdo->fromJSON($graph);
1883          }
1884          /* graph as an array */
1885          if (is_array($graph)) {
1886              foreach ($graph as $alias => $branch) {
1887                  $fkMeta = $this->getFKDefinition($alias);
1888                  if ($fkMeta) {
1889                      $fkCriteria = isset($fkMeta['criteria']) && isset($fkMeta['criteria']['foreign']) ? $fkMeta['criteria']['foreign'] : null;
1890                      if ($criteria === null) {
1891                          $query= array($fkMeta['foreign'] => $this->get($fkMeta['local']));
1892                          if ($fkCriteria !== null) {
1893                              $query= array($fkCriteria, $query);
1894                          }
1895                      } else {
1896                          $query= $this->xpdo->newQuery($fkMeta['class'], $criteria);
1897                          $addCriteria= array("{$query->getAlias()}.{$fkMeta['foreign']}" => $this->get($fkMeta['local']));
1898                          if ($fkCriteria !== null) {
1899                              $fkAddCriteria = array();
1900                              foreach ($fkCriteria as $fkCritKey => $fkCritVal) {
1901                                  if (is_numeric($fkCritKey)) continue;
1902                                  $fkAddCriteria["{$criteria->getAlias()}.{$fkCritKey}"] = $fkCritVal;
1903                              }
1904                              if (!empty($fkAddCriteria)) {
1905                                  $addCriteria = array($fkAddCriteria, $addCriteria);
1906                              }
1907                          }
1908                          $query->andCondition($addCriteria);
1909                      }
1910                      $collection = $this->xpdo->call($fkMeta['class'], 'loadCollectionGraph', array(
1911                          &$this->xpdo,
1912                          $fkMeta['class'],
1913                          $branch,
1914                          $query,
1915                          $cacheFlag
1916                      ));
1917                      if (!empty($collection)) {
1918                          if ($fkMeta['cardinality'] == 'many') {
1919                              $this->_relatedObjects[$alias] = $collection;
1920                          } else {
1921                              $this->_relatedObjects[$alias] = reset($collection);
1922                          }
1923                      }
1924                  }
1925              }
1926          } else {
1927              $graph = false;
1928          }
1929          return $graph;
1930      }
1931  
1932      /**
1933       * Copies the object fields and corresponding values to an associative array.
1934       *
1935       * @param string $keyPrefix An optional prefix to prepend to the field values.
1936       * @param boolean $rawValues An optional flag indicating if you want the raw values instead of
1937       * those returned by the {@link xPDOObject::get()} function.
1938       * @param boolean $excludeLazy An option flag indicating if you want to exclude lazy fields from
1939       * the resulting array; the default behavior is to include them which means the object will
1940       * query the database for the lazy fields before providing the value.
1941       * @param boolean|integer|string|array $includeRelated Describes if and how to include loaded related object fields.
1942       * As an integer all loaded related objects in the graph up to that level of depth will be included.
1943       * As a string, only loaded related objects matching the JSON graph representation will be included.
1944       * As an array, only loaded related objects matching the graph array will be included.
1945       * As boolean true, all currently loaded related objects will be included.
1946       * @return array An array representation of the object fields/values.
1947       */
1948      public function toArray($keyPrefix= '', $rawValues= false, $excludeLazy= false, $includeRelated= false) {
1949          $objArray= null;
1950          if (is_array($this->_fields)) {
1951              $keys= array_keys($this->_fields);
1952              if (!$excludeLazy && $this->isLazy()) {
1953                  $this->_loadFieldData($this->_lazy);
1954              }
1955              foreach ($keys as $key) {
1956                  if (!$excludeLazy || !$this->isLazy($key)) {
1957                      $objArray[$keyPrefix . $key]= $rawValues ? $this->_fields[$key] : $this->get($key);
1958                  }
1959              }
1960          }
1961          if (!empty($includeRelated)) {
1962              $graph = null;
1963              if (is_int($includeRelated) && $includeRelated > 0) {
1964                  $graph = $this->xpdo->getGraph($this->_class, $includeRelated);
1965              } elseif (is_string($includeRelated)) {
1966                  $graph = $this->xpdo->fromJSON($includeRelated);
1967              } elseif (is_array($includeRelated)) {
1968                  $graph = $includeRelated;
1969              }
1970              if ($includeRelated === true || is_array($graph)) {
1971                  foreach ($this->_relatedObjects as $alias => $branch) {
1972                      if ($includeRelated === true || array_key_exists($alias, $graph)) {
1973                          if (is_array($branch)){
1974                              foreach($branch as $pk => $obj){
1975                                  $objArray[$alias][$pk] = $obj->toArray($keyPrefix, $rawValues, $excludeLazy, $includeRelated === true ? true : $graph[$alias]);
1976                              }
1977                          } elseif ($branch instanceof xPDOObject) {
1978                              $objArray[$alias] = $branch->toArray($keyPrefix, $rawValues, $excludeLazy, $includeRelated === true ? true : $graph[$alias]);
1979                          }
1980                      }
1981                  }
1982              }
1983          }
1984          return $objArray;
1985      }
1986  
1987      /**
1988       * Sets object fields from an associative array of key => value pairs.
1989       *
1990       * @param array $fldarray An associative array of key => values.
1991       * @param string $keyPrefix Specify an optional prefix to strip from all array
1992       * keys in fldarray.
1993       * @param boolean $setPrimaryKeys Optional param to set generated primary keys.
1994       * @param boolean $rawValues Optional way to set values without calling the
1995       * {@link xPDOObject::set()} method.
1996       * @param boolean $adhocValues Optional way to set adhoc values so that all the values of
1997       * fldarray become object vars.
1998       */
1999      public function fromArray($fldarray, $keyPrefix= '', $setPrimaryKeys= false, $rawValues= false, $adhocValues= false) {
2000          if (is_array($fldarray)) {
2001              $pkSet= false;
2002              $generatedKey= false;
2003              reset($fldarray);
2004              while (list ($key, $val)= each($fldarray)) {
2005                  if (!empty ($keyPrefix)) {
2006                      $prefixPos= strpos($key, $keyPrefix);
2007                      if ($prefixPos === 0) {
2008                          $key= substr($key, strlen($keyPrefix));
2009                      } else {
2010                          continue;
2011                      }
2012                      if ($this->xpdo->getDebug() === true) $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Stripped prefix {$keyPrefix} to produce key {$key}");
2013                  }
2014                  $key = $this->getField($key);
2015                  if (isset ($this->_fieldMeta[$key]['index']) && $this->_fieldMeta[$key]['index'] == 'pk') {
2016                      if ($setPrimaryKeys) {
2017                          if (isset ($this->_fieldMeta[$key]['generated'])) {
2018                              $generatedKey= true;
2019                          }
2020                          if ($this->_new) {
2021                              if ($rawValues || $generatedKey) {
2022                                  $this->_setRaw($key, $val);
2023                              } else {
2024                                  $this->set($key, $val);
2025                              }
2026                              $pkSet= true;
2027                          }
2028                      }
2029                  }
2030                  elseif (isset ($this->_fieldMeta[$key])) {
2031                      if ($rawValues) {
2032                          $this->_setRaw($key, $val);
2033                      } else {
2034                          $this->set($key, $val);
2035                      }
2036                  }
2037                  elseif ($adhocValues || $this->getOption(xPDO::OPT_HYDRATE_ADHOC_FIELDS)) {
2038                      if ($rawValues) {
2039                          $this->_setRaw($key, $val);
2040                      } else {
2041                          $this->set($key, $val);
2042                      }
2043                  }
2044                  if ($this->isLazy($key)) {
2045                      $this->_lazy = array_diff($this->_lazy, array($key));
2046                  }
2047              }
2048          }
2049      }
2050  
2051      /**
2052       * Add a validation rule to an object field for this instance.
2053       *
2054       * @param string $field The field key to apply the rule to.
2055       * @param string $name A name to identify the rule.
2056       * @param string $type The type of rule.
2057       * @param string $rule The rule definition.
2058       * @param array $parameters Any input parameters for the rule.
2059       */
2060      public function addValidationRule($field, $name, $type, $rule, array $parameters= array()) {
2061          $field = $this->getField($field);
2062          if (is_string($field)) {
2063              if (!$this->_validationLoaded) $this->_loadValidation();
2064              if (!isset($this->_validationRules[$field])) $this->_validationRules[$field]= array();
2065              $this->_validationRules[$field][$name]= array(
2066                  'type' => $type,
2067                  'rule' => $rule,
2068                  'parameters' => array()
2069              );
2070              foreach ($parameters as $paramKey => $paramValue) {
2071                  $this->_validationRules[$field][$name]['parameters'][$paramKey]= $paramValue;
2072              }
2073          }
2074      }
2075  
2076      /**
2077       * Remove one or more validation rules from this instance.
2078       *
2079       * @param string $field An optional field name to remove rules from. If not
2080       * specified or null, all rules from all columns will be removed.
2081       * @param array $rules An optional array of rule names to remove if a single
2082       * field is specified.  If $field is null, this parameter is ignored.
2083       */
2084      public function removeValidationRules($field = null, array $rules = array()) {
2085          if (!$this->_validationLoaded) $this->_loadValidation();
2086          if (empty($rules) && is_string($field)) {
2087              unset($this->_validationRules[$this->getField($field)]);
2088          } elseif (empty($rules) && is_null($field)) {
2089              $this->_validationRules = array();
2090          } elseif (is_array($rules) && !empty($rules) && is_string($field)) {
2091              $field = $this->getField($field);
2092              foreach ($rules as $name) {
2093                  unset($this->_validationRules[$field][$name]);
2094              }
2095          }
2096      }
2097  
2098      /**
2099       * Get the xPDOValidator class configured for this instance.
2100       *
2101       * @return string|boolean The xPDOValidator instance or false if it could
2102       * not be loaded.
2103       */
2104      public function getValidator() {
2105          if (!is_object($this->_validator)) {
2106              $validatorClass = $this->xpdo->loadClass('validation.xPDOValidator', XPDO_CORE_PATH, true, true);
2107              if ($derivedClass = $this->getOption(xPDO::OPT_VALIDATOR_CLASS, null, '')) {
2108                  if ($derivedClass = $this->xpdo->loadClass($derivedClass, '', false, true)) {
2109                      $validatorClass = $derivedClass;
2110                  }
2111              }
2112              if ($validatorClass) {
2113                  $this->_validator= new $validatorClass($this);
2114              }
2115          }
2116          return $this->_validator;
2117      }
2118  
2119      /**
2120       * Used to load validation from the object map.
2121       *
2122       * @access public
2123       * @param boolean $reload Indicates if the schema validation rules should be
2124       * reloaded.
2125       */
2126      public function _loadValidation($reload= false) {
2127          if (!$this->_validationLoaded || $reload == true) {
2128              $validation= $this->xpdo->getValidationRules($this->_class);
2129              $this->_validationLoaded= true;
2130              foreach ($validation as $column => $rules) {
2131                  foreach ($rules as $name => $rule) {
2132                      $parameters = array_diff($rule, array($rule['type'], $rule['rule']));
2133                      $this->addValidationRule($column, $name, $rule['type'], $rule['rule'], $parameters);
2134                  }
2135              }
2136          }
2137      }
2138  
2139      /**
2140       * Validate the field values using an xPDOValidator.
2141       *
2142       * @param array $options An array of options to pass to the validator.
2143       * @return boolean True if validation was successful.
2144       */
2145      public function validate(array $options = array()) {
2146          $validated= false;
2147          if ($validator= $this->getValidator()) {
2148              $validated= $this->_validator->validate($options);
2149              if ($this->xpdo->getDebug() === true) {
2150                  $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Validator class executed, result = " . print_r($validated, true));
2151              }
2152          } else {
2153              if ($this->xpdo->getDebug() === true) {
2154                  $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "No validator found for {$this->_class} instance.");
2155              }
2156              $validated= true;
2157          }
2158          return $validated;
2159      }
2160  
2161      /**
2162       * Indicates if the object or specified field has been validated.
2163       *
2164       * @param string $key Optional key to check for specific validation.
2165       * @return boolean True if the object or specified field has been fully
2166       * validated successfully.
2167       */
2168      public function isValidated($key= '') {
2169          $unvalidated = array_diff($this->_dirty, $this->_validated);
2170          if (empty($key)) {
2171              $validated = (count($unvalidated) > 0);
2172          } else {
2173              $validated = !in_array($this->getField($key), $unvalidated);
2174          }
2175          return $validated;
2176      }
2177  
2178      /**
2179       * Indicates if the object or specified field is lazy.
2180       *
2181       * @param string $key Optional key to check for laziness.
2182       * @return boolean True if the field specified or if any field is lazy if no
2183       * field is specified.
2184       */
2185      public function isLazy($key= '') {
2186          $lazy = false;
2187          if (empty($key)) {
2188              $lazy = (count($this->_lazy) > 0);
2189          } else {
2190              $key = $this->getField($key, true);
2191              if ($key !== false) {
2192                  $lazy = in_array($key, $this->_lazy);
2193              }
2194          }
2195          return $lazy;
2196      }
2197  
2198      /**
2199       * Gets related objects by a foreign key and specified criteria.
2200       *
2201       * @access protected
2202       * @param string $alias The alias representing the relationship.
2203       * @param mixed An optional xPDO criteria expression.
2204       * @param boolean|integer Indicates if the saved object(s) should
2205       * be cached and optionally, by specifying an integer value, for how many
2206       * seconds before expiring.  Overrides the cacheFlag for the object.
2207       * @return array A collection of objects matching the criteria.
2208       */
2209      protected function & _getRelatedObjectsByFK($alias, $criteria= null, $cacheFlag= true) {
2210          $collection= array ();
2211          if (isset($this->_relatedObjects[$alias]) && (is_object($this->_relatedObjects[$alias]) || (is_array($this->_relatedObjects[$alias]) && !empty ($this->_relatedObjects[$alias])))) {
2212              $collection= & $this->_relatedObjects[$alias];
2213          } else {
2214              $fkMeta= $this->getFKDefinition($alias);
2215              if ($fkMeta) {
2216                  $fkCriteria = isset($fkMeta['criteria']) && isset($fkMeta['criteria']['foreign']) ? $fkMeta['criteria']['foreign'] : null;
2217                  if ($criteria === null) {
2218                      $criteria= array($fkMeta['foreign'] => $this->get($fkMeta['local']));
2219                      if ($fkCriteria !== null) {
2220                          $criteria= array($fkCriteria, $criteria);
2221                      }
2222                  } else {
2223                      $criteria= $this->xpdo->newQuery($fkMeta['class'], $criteria);
2224                      $addCriteria = array("{$criteria->getAlias()}.{$fkMeta['foreign']}" => $this->get($fkMeta['local']));
2225                      if ($fkCriteria !== null) {
2226                          $fkAddCriteria = array();
2227                          foreach ($fkCriteria as $fkCritKey => $fkCritVal) {
2228                              if (is_numeric($fkCritKey)) continue;
2229                              $fkAddCriteria["{$criteria->getAlias()}.{$fkCritKey}"] = $fkCritVal;
2230                          }
2231                          if (!empty($fkAddCriteria)) {
2232                              $addCriteria = array($fkAddCriteria, $addCriteria);
2233                          }
2234                      }
2235                      $criteria->andCondition($addCriteria);
2236                  }
2237                  if ($collection= $this->xpdo->getCollection($fkMeta['class'], $criteria, $cacheFlag)) {
2238                      $this->_relatedObjects[$alias]= array_diff_key($this->_relatedObjects[$alias], $collection) + $collection;
2239                  }
2240              }
2241          }
2242          if ($this->xpdo->getDebug() === true) {
2243              $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "_getRelatedObjectsByFK :: {$alias} :: " . (is_object($criteria) ? print_r($criteria->sql, true)."\n".print_r($criteria->bindings, true) : 'no criteria'));
2244          }
2245          return $collection;
2246      }
2247  
2248      /**
2249       * Initializes the field names with the qualified table name.
2250       *
2251       * Once this is called, you can lookup the qualified name by the field name
2252       * itself in {@link xPDOObject::$fieldNames}.
2253       *
2254       * @access protected
2255       */
2256      protected function _initFields() {
2257          reset($this->_fieldMeta);
2258          while (list ($k, $v)= each($this->_fieldMeta)) {
2259              $this->fieldNames[$k]= $this->xpdo->escape($this->_table) . '.' . $this->xpdo->escape($k);
2260          }
2261      }
2262  
2263      /**
2264       * Returns a JSON representation of the object.
2265       *
2266       * @param string $keyPrefix An optional prefix to prepend to the field keys.
2267       * @param boolean $rawValues An optional flag indicating if the field values
2268       * should be returned raw or via {@link xPDOObject::get()}.
2269       * @return string A JSON string representing the object.
2270       */
2271      public function toJSON($keyPrefix= '', $rawValues= false) {
2272          $json= '';
2273          $array= $this->toArray($keyPrefix, $rawValues);
2274          if ($array) {
2275              $json= $this->xpdo->toJSON($array);
2276          }
2277          return $json;
2278      }
2279  
2280      /**
2281       * Sets the object fields from a JSON object string.
2282       *
2283       * @param string $jsonSource A JSON object string.
2284       * @param string $keyPrefix An optional prefix to strip from the keys.
2285       * @param boolean $setPrimaryKeys Indicates if primary key fields should be set.
2286       * @param boolean $rawValues Indicates if values should be set raw or via
2287       * {@link xPDOObject::set()}.
2288       * @param boolean $adhocValues Indicates if ad hoc fields should be added to the
2289       * xPDOObject from the source object.
2290       */
2291      public function fromJSON($jsonSource, $keyPrefix= '', $setPrimaryKeys= false, $rawValues= false, $adhocValues= false) {
2292          $array= $this->xpdo->fromJSON($jsonSource, true);
2293          if ($array) {
2294              $this->fromArray($array, $keyPrefix, $setPrimaryKeys, $rawValues, $adhocValues);
2295          } else {
2296              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'xPDOObject::fromJSON() -- Could not convert jsonSource to a PHP array.');
2297          }
2298      }
2299  
2300      /**
2301       * Encodes a string using the specified algorithm.
2302       *
2303       * NOTE: This implementation currently only implements md5.  To implement additional
2304       * algorithms, override this function in your xPDOObject derivative classes.
2305       *
2306       * @param string $source The string source to encode.
2307       * @param string $type The type of encoding algorithm to apply, md5 by default.
2308       * @return string The encoded string.
2309       */
2310      public function encode($source, $type= 'md5') {
2311          if (!is_string($source) || empty ($source)) {
2312              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'xPDOObject::encode() -- Attempt to encode source data that is not a string (or is empty); encoding skipped.');
2313              return $source;
2314          }
2315          switch ($type) {
2316              case 'password':
2317              case 'md5':
2318                  $encoded= md5($source);
2319                  break;
2320              default :
2321                  $encoded= $source;
2322                  $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOObject::encode() -- Attempt to encode source data using an unsupported encoding algorithm ({$type}).");
2323                  break;
2324          }
2325          return $encoded;
2326      }
2327  
2328      /**
2329       * Indicates if an object field has been modified (or never saved).
2330       *
2331       * @access public
2332       * @param string $key The field name to check.
2333       * @return boolean True if the field exists and either has been modified or the object is new.
2334       */
2335      public function isDirty($key) {
2336          $dirty= false;
2337          $actualKey = $this->getField($key, true);
2338          if ($actualKey !== false) {
2339              if (array_key_exists($actualKey, $this->_dirty) || $this->isNew()) {
2340                  $dirty= true;
2341              }
2342          } else {
2343              $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOObject::isDirty() -- Attempt to check if an unknown field ({$key}) has been modified.");
2344          }
2345          return $dirty;
2346      }
2347  
2348      /**
2349       * Add the field to a collection of field keys that have been modified.
2350       *
2351       * This function also clears any validation flag associated with the field.
2352       *
2353       * @param string $key The key of the field to set dirty.
2354       */
2355      public function setDirty($key= '') {
2356          if (empty($key)) {
2357              foreach (array_keys($this->_fieldMeta) as $fIdx => $fieldKey) {
2358                  $this->setDirty($fieldKey);
2359              }
2360          }
2361          else {
2362              $key = $this->getField($key, true);
2363              if ($key !== false) {
2364                  $this->_dirty[$key] = $key;
2365                  if (isset($this->_validated[$key])) unset($this->_validated[$key]);
2366              }
2367          }
2368      }
2369  
2370      /**
2371       * Indicates if the instance is new, and has not yet been persisted.
2372       *
2373       * @return boolean True if the object has not been saved or was loaded from
2374       * the database.
2375       */
2376      public function isNew() {
2377          return (boolean) $this->_new;
2378      }
2379  
2380      /**
2381       * Gets the database data type for the specified field.
2382       *
2383       * @access protected
2384       * @param string $key The field name to get the data type for.
2385       * @return string The DB data type of the field.
2386       */
2387      protected function _getDataType($key) {
2388          $type= 'text';
2389          $actualKey = $this->getField($key, true);
2390          if ($actualKey !== false && isset($this->_fieldMeta[$actualKey]['dbtype'])) {
2391              $type= strtolower($this->_fieldMeta[$actualKey]['dbtype']);
2392          } elseif ($this->xpdo->getDebug() === true) {
2393              $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "xPDOObject::_getDataType() -- No data type specified for field ({$key}), using `text`.");
2394          }
2395          return $type;
2396      }
2397  
2398      /**
2399       * Gets the php data type for the specified field.
2400       *
2401       * @access protected
2402       * @param string $key The field name to get the data type for.
2403       * @return string The PHP data type of the field.
2404       */
2405      protected function _getPHPType($key) {
2406          $type= 'string';
2407          $actualKey = $this->getField($key, true);
2408          if ($actualKey !== false && isset($this->_fieldMeta[$actualKey]['phptype'])) {
2409              $type= strtolower($this->_fieldMeta[$actualKey]['phptype']);
2410          } elseif ($this->xpdo->getDebug() === true) {
2411              $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "xPDOObject::_getPHPType() -- No PHP type specified for field ({$key}), using `string`.");
2412          }
2413          return $type;
2414      }
2415  
2416      /**
2417       * Load persistent data from the source for the field(s) indicated.
2418       *
2419       * @access protected
2420       * @param string|array $fields A field name or array of field names to load
2421       * from the data source.
2422       */
2423      protected function _loadFieldData($fields) {
2424          if (!is_array($fields)) $fields= array($fields);
2425          else $fields= array_values($fields);
2426          $criteria= $this->xpdo->newQuery($this->_class, $this->getPrimaryKey());
2427          $criteria->select($fields);
2428          if ($rows= xPDOObject :: _loadRows($this->xpdo, $this->_class, $criteria)) {
2429              $row= $rows->fetch(PDO::FETCH_ASSOC);
2430              $rows->closeCursor();
2431              $this->fromArray($row, '', false, true);
2432              $this->_lazy= array_diff($this->_lazy, $fields);
2433          }
2434      }
2435  
2436      /**
2437       * Set a raw value on a field converted to the appropriate type.
2438       *
2439       * @access protected
2440       * @param string $key The key identifying the field to set.
2441       * @param mixed $val The value to set.
2442       * @return boolean Returns true if the value was set, false otherwise.
2443       */
2444      protected function _setRaw($key, $val) {
2445          $set = false;
2446          if ($val === null) {
2447              $this->_fields[$key] = null;
2448              $set = true;
2449          } else {
2450              $phptype = $this->_getPHPType($key);
2451              $dbtype = $this->_getDataType($key);
2452              switch ($phptype) {
2453                  case 'int':
2454                  case 'integer':
2455                  case 'boolean':
2456                      $this->_fields[$key] = (integer) $val;
2457                      $set = true;
2458                      break;
2459                  case 'float':
2460                      $this->_fields[$key] = (float) $val;
2461                      $set = true;
2462                      break;
2463                  case 'array':
2464                      if (is_array($val)) {
2465                          $this->_fields[$key]= serialize($val);
2466                          $set = true;
2467                      } elseif (is_string($val)) {
2468                          $this->_fields[$key]= $val;
2469                          $set = true;
2470                      } elseif (is_object($val) && $val instanceof xPDOObject) {
2471                          $this->_fields[$key]= serialize($val->toArray());
2472                          $set = true;
2473                      }
2474                      break;
2475                  case 'json':
2476                      if (!is_string($val)) {
2477                          $v = $val;
2478                          if (is_array($v)) {
2479                              $this->_fields[$key] = $this->xpdo->toJSON($v);
2480                              $set = true;
2481                          } elseif (is_object($v) && $v instanceof xPDOObject) {
2482                              $this->_fields[$key] = $this->xpdo->toJSON($v->toArray());
2483                              $set = true;
2484                          }
2485                      } else {
2486                          $this->_fields[$key]= $val;
2487                          $set = true;
2488                      }
2489                      break;
2490                  case 'date':
2491                  case 'datetime':
2492                  case 'timestamp':
2493                      if (preg_match('/int/i', $dbtype)) {
2494                          $this->_fields[$key] = (integer) $val;
2495                          $set = true;
2496                          break;
2497                      }
2498                  default:
2499                      $this->_fields[$key] = $val;
2500                      $set = true;
2501              }
2502          }
2503          if ($set) $this->setDirty($key);
2504          return $set;
2505      }
2506  
2507      /**
2508       * Find aliases for any defined object relations of the specified class.
2509       *
2510       * @access protected
2511       * @param string $class The name of the class to find aliases from.
2512       * @param int $limit An optional limit on the number of aliases to return;
2513       * default is 0, i.e. no limit.
2514       * @return array An array of aliases or an empty array if none are found.
2515       */
2516      protected function _getAliases($class, $limit = 0) {
2517          $aliases = array();
2518          $limit = intval($limit);
2519          $array = array('aggregates' => $this->_aggregates, 'composites' => $this->_composites);
2520          foreach ($array as $relType => $relations) {
2521              foreach ($relations as $alias => $def) {
2522                  if (isset($def['class']) && $def['class'] == $class) {
2523                      $aliases[] = $alias;
2524                      if ($limit > 0 && count($aliases) > $limit) break;
2525                  }
2526              }
2527          }
2528          return $aliases;
2529      }
2530  }
2531  
2532  /**
2533   * Extend to define a class with a native integer primary key field named id.
2534   *
2535   * @see xpdo/om/mysql/xpdosimpleobject.map.inc.php
2536   * @package xpdo
2537   * @subpackage om
2538   */
2539  class xPDOSimpleObject extends xPDOObject {}

title

Description

title

Description

title

Description

title

title

Body