PageRenderTime 58ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/modx/core/xpdo/om/xpdoobject.class.php

https://bitbucket.org/argnist/mohana
PHP | 2192 lines | 1449 code | 76 blank | 667 comment | 528 complexity | 5596bac0e53ac49189027212d5efcc41 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.1, GPL-2.0, GPL-3.0, LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * Copyright 2010-2011 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. * The base persistent xPDO object classes.
  22. *
  23. * This file contains the base persistent object classes, which your user-
  24. * defined classes will extend when implementing an xPDO object model.
  25. *
  26. * @package xpdo
  27. * @subpackage om
  28. */
  29. /**
  30. * The base persistent xPDO object class.
  31. *
  32. * This is the basis for the entire xPDO object model, and can also be used by a
  33. * class generator {@link xPDOGenerator}, ultimately allowing custom classes to
  34. * be user-defined in a web interface and framework-generated at runtime.
  35. *
  36. * @abstract This is an abstract class, and is not represented by an actual
  37. * table; it simply defines the member variables and functions needed for object
  38. * persistence. All xPDOObject derivatives must define both a PHP 4 style
  39. * constructor which calls a PHP 5 style __construct() method with the same
  40. * parameters. This is necessary to allow instantiation of further derived
  41. * classes without knowing the name of the class ahead of time in PHP 4. Note
  42. * that this does not meet E_STRICT compliance in PHP 5, but is the only sane
  43. * way to achieve consistency between the PHP 4 and 5 inheritence models.
  44. *
  45. * @package xpdo
  46. * @subpackage om
  47. */
  48. class xPDOObject {
  49. /**
  50. * A convenience reference to the xPDO object.
  51. * @var xPDO
  52. * @access public
  53. */
  54. public $xpdo= null;
  55. /**
  56. * Name of the data source container the object belongs to.
  57. * @var string
  58. * @access public
  59. */
  60. public $container= null;
  61. /**
  62. * Names of the fields in the data table, fully-qualified with a table name.
  63. *
  64. * NOTE: For use in table joins to qualify fields with the same name.
  65. *
  66. * @var array
  67. * @access public
  68. */
  69. public $fieldNames= null;
  70. /**
  71. * The actual class name of an instance.
  72. * @var string
  73. */
  74. public $_class= null;
  75. /**
  76. * The package the class is a part of.
  77. * @var string
  78. */
  79. public $_package= null;
  80. /**
  81. * An alias for this instance of the class.
  82. * @var string
  83. */
  84. public $_alias= null;
  85. /**
  86. * The primary key field (or an array of primary key fields) for this object.
  87. * @var string|array
  88. * @access public
  89. */
  90. public $_pk= null;
  91. /**
  92. * The php native type of the primary key field.
  93. *
  94. * NOTE: Will be an array if multiple primary keys are specified for the object.
  95. *
  96. * @var string|array
  97. * @access public
  98. */
  99. public $_pktype= null;
  100. /**
  101. * Name of the actual table representing this class.
  102. * @var string
  103. * @access public
  104. */
  105. public $_table= null;
  106. /**
  107. * An array of meta data for the table.
  108. * @var string
  109. * @access public
  110. */
  111. public $_tableMeta= null;
  112. /**
  113. * An array of field names that have been modified.
  114. * @var array
  115. * @access public
  116. */
  117. public $_dirty= array ();
  118. /**
  119. * An array of field names that have not been loaded from the source.
  120. * @var array
  121. * @access public
  122. */
  123. public $_lazy= array ();
  124. /**
  125. * An array of key-value pairs representing the fields of the instance.
  126. * @var array
  127. * @access public
  128. */
  129. public $_fields= array ();
  130. /**
  131. * An array of metadata definitions for each field in the class.
  132. * @var array
  133. * @access public
  134. */
  135. public $_fieldMeta= array ();
  136. /**
  137. * An array of aggregate foreign key relationships for the class.
  138. * @var array
  139. * @access public
  140. */
  141. public $_aggregates= array ();
  142. /**
  143. * An array of composite foreign key relationships for the class.
  144. * @var array
  145. * @access public
  146. */
  147. public $_composites= array ();
  148. /**
  149. * An array of object instances related to this object instance.
  150. * @var array
  151. * @access public
  152. */
  153. public $_relatedObjects= array ();
  154. /**
  155. * A validator object responsible for this object instance.
  156. * @var xPDOValidator
  157. * @access public
  158. */
  159. public $_validator = null;
  160. /**
  161. * An array of validation rules for this object instance.
  162. * @var array
  163. * @access public
  164. */
  165. public $_validationRules = array();
  166. /**
  167. * An array of field names that have been already validated.
  168. * @var array
  169. * @access public
  170. */
  171. public $_validated= array ();
  172. /**
  173. * Indicates if the validation map has been loaded.
  174. * @var boolean
  175. * @access public
  176. */
  177. public $_validationLoaded= false;
  178. /**
  179. * Indicates if the instance is transient (and thus new).
  180. * @var boolean
  181. * @access public
  182. */
  183. public $_new= true;
  184. /**
  185. * Indicates the cacheability of the instance.
  186. * @var boolean
  187. */
  188. public $_cacheFlag= true;
  189. /**
  190. * A collection of various options that can be used on the instance.
  191. * @var array
  192. */
  193. public $_options= array();
  194. /**
  195. * Responsible for loading a result set from the database.
  196. *
  197. * @static
  198. * @param xPDO &$xpdo A valid xPDO instance.
  199. * @param string $className Name of the class.
  200. * @param xPDOCriteria $criteria A valid xPDOCriteria instance.
  201. * @return PDOStatement A reference to a PDOStatement representing the
  202. * result set.
  203. */
  204. public static function & _loadRows(& $xpdo, $className, $criteria) {
  205. $rows= null;
  206. if ($criteria->prepare()) {
  207. 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));
  208. $tstart= $xpdo->getMicroTime();
  209. if (!$criteria->stmt->execute()) {
  210. $tend= $xpdo->getMicroTime();
  211. $totaltime= $tend - $tstart;
  212. $xpdo->queryTime= $xpdo->queryTime + $totaltime;
  213. $xpdo->executedQueries= $xpdo->executedQueries + 1;
  214. $errorInfo= $criteria->stmt->errorInfo();
  215. $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error ' . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($errorInfo, true));
  216. if (($errorInfo[1] == '1146' || $errorInfo[1] == '1') && $xpdo->getOption(xPDO::OPT_AUTO_CREATE_TABLES)) {
  217. if ($xpdo->getManager() && $xpdo->manager->createObjectContainer($className)) {
  218. $tstart= $xpdo->getMicroTime();
  219. if (!$criteria->stmt->execute()) {
  220. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($criteria->stmt->errorInfo(), true));
  221. }
  222. $tend= $xpdo->getMicroTime();
  223. $totaltime= $tend - $tstart;
  224. $xpdo->queryTime= $xpdo->queryTime + $totaltime;
  225. $xpdo->executedQueries= $xpdo->executedQueries + 1;
  226. } else {
  227. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $xpdo->errorCode() . " attempting to create object container for class {$className}:\n" . print_r($xpdo->errorInfo(), true));
  228. }
  229. }
  230. }
  231. $rows= & $criteria->stmt;
  232. } else {
  233. $errorInfo = $xpdo->errorInfo();
  234. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error preparing statement for query: {$criteria->sql} - " . print_r($errorInfo, true));
  235. if (($errorInfo[1] == '1146' || $errorInfo[1] == '1') && $xpdo->getOption(xPDO::OPT_AUTO_CREATE_TABLES)) {
  236. if ($xpdo->getManager() && $xpdo->manager->createObjectContainer($className)) {
  237. if (!$criteria->prepare()) {
  238. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error preparing statement for query: {$criteria->sql} - " . print_r($errorInfo, true));
  239. } else {
  240. $tstart= $xpdo->getMicroTime();
  241. if (!$criteria->stmt->execute()) {
  242. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $criteria->stmt->errorCode() . " executing statement: \n" . print_r($criteria->stmt->errorInfo(), true));
  243. }
  244. $tend= $xpdo->getMicroTime();
  245. $totaltime= $tend - $tstart;
  246. $xpdo->queryTime= $xpdo->queryTime + $totaltime;
  247. $xpdo->executedQueries= $xpdo->executedQueries + 1;
  248. }
  249. } else {
  250. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error " . $xpdo->errorCode() . " attempting to create object container for class {$className}:\n" . print_r($xpdo->errorInfo(), true));
  251. }
  252. }
  253. }
  254. return $rows;
  255. }
  256. /**
  257. * Loads an instance from an associative array.
  258. *
  259. * @static
  260. * @param xPDO &$xpdo A valid xPDO instance.
  261. * @param string $className Name of the class.
  262. * @param xPDOQuery|string $criteria A valid xPDOQuery instance or relation alias.
  263. * @param array $row The associative array containing the instance data.
  264. * @return xPDOObject A new xPDOObject derivative representing a data row.
  265. */
  266. public static function _loadInstance(& $xpdo, $className, $criteria, $row) {
  267. $rowPrefix= '';
  268. if (is_object($criteria) && $criteria instanceof xPDOQuery) {
  269. $alias = $criteria->getAlias();
  270. $actualClass = $criteria->getClass();
  271. } elseif (is_string($criteria) && !empty($criteria)) {
  272. $alias = $criteria;
  273. $actualClass = $className;
  274. } else {
  275. $alias = $className;
  276. $actualClass= $className;
  277. }
  278. if (isset($row["{$className}_class_key"])) {
  279. $actualClass= $row["{$className}_class_key"];
  280. $rowPrefix= $className . '_';
  281. }
  282. elseif (isset ($row["{$alias}_class_key"])) {
  283. $actualClass= $row["{$alias}_class_key"];
  284. $rowPrefix= $alias . '_';
  285. }
  286. elseif (isset ($row['class_key'])) {
  287. $actualClass= $row['class_key'];
  288. }
  289. $instance= $xpdo->newObject($actualClass);
  290. if (is_object($instance) && $instance instanceof xPDOObject) {
  291. if (strpos(strtolower(key($row)), strtolower($alias . '_')) === 0) {
  292. $rowPrefix= $alias . '_';
  293. }
  294. elseif (strpos(strtolower(key($row)), strtolower($className . '_')) === 0) {
  295. $rowPrefix= $className . '_';
  296. }
  297. else {
  298. $pk = $xpdo->getPK($actualClass);
  299. if (is_array($pk)) $pk = reset($pk);
  300. if (isset($row["{$alias}_{$pk}"])) {
  301. $rowPrefix= $alias . '_';
  302. }
  303. elseif ($actualClass !== $className && $actualClass !== $alias && isset($row["{$actualClass}_{$pk}"])) {
  304. $rowPrefix= $actualClass . '_';
  305. }
  306. elseif ($className !== $alias && isset($row["{$className}_{$pk}"])) {
  307. $rowPrefix= $className . '_';
  308. }
  309. }
  310. if (!$instance instanceof $className) {
  311. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Instantiated a derived class {$actualClass} that is not a subclass of the requested class {$className}");
  312. }
  313. $instance->_lazy= $actualClass !== $className ? array_keys($xpdo->getFields($className)) : array_keys($instance->_fields);
  314. $instance->fromArray($row, $rowPrefix, true, true);
  315. $instance->_dirty= array ();
  316. $instance->_new= false;
  317. }
  318. return $instance;
  319. }
  320. /**
  321. * Responsible for loading an instance into a collection.
  322. *
  323. * @static
  324. * @param xPDO &$xpdo A valid xPDO instance.
  325. * @param array &$objCollection The collection to load the instance into.
  326. * @param string $className Name of the class.
  327. * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
  328. * @param boolean|integer $cacheFlag Indicates if the objects should be cached and
  329. * optionally, by specifying an integer value, for how many seconds.
  330. */
  331. public static function _loadCollectionInstance(xPDO & $xpdo, array & $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag=true) {
  332. $loaded = false;
  333. if ($obj= xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row)) {
  334. if (($cacheKey= $obj->getPrimaryKey()) && !$obj->isLazy()) {
  335. if (is_array($cacheKey)) {
  336. $pkval= implode('-', $cacheKey);
  337. } else {
  338. $pkval= $cacheKey;
  339. }
  340. if ($xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1) == 2 && $xpdo->_cacheEnabled && $cacheFlag) {
  341. if (!$fromCache) {
  342. $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
  343. $xpdo->toCache($pkCriteria, $obj, $cacheFlag);
  344. } else {
  345. $obj->_cacheFlag= true;
  346. }
  347. }
  348. $objCollection[$pkval]= $obj;
  349. $loaded = true;
  350. } else {
  351. $objCollection[]= $obj;
  352. $loaded = true;
  353. }
  354. }
  355. return $loaded;
  356. }
  357. /**
  358. * Load an instance of an xPDOObject or derivative class.
  359. *
  360. * @static
  361. * @param xPDO &$xpdo A valid xPDO instance.
  362. * @param string $className Name of the class.
  363. * @param mixed $criteria A valid primary key, criteria array, or
  364. * xPDOCriteria instance.
  365. * @param boolean|integer $cacheFlag Indicates if the objects should be
  366. * cached and optionally, by specifying an integer value, for how many
  367. * seconds.
  368. * @return object|null An instance of the requested class, or null if it
  369. * could not be instantiated.
  370. */
  371. public static function load(xPDO & $xpdo, $className, $criteria, $cacheFlag= true) {
  372. $instance= null;
  373. $fromCache= false;
  374. if ($className= $xpdo->loadClass($className)) {
  375. if (!is_object($criteria)) {
  376. $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
  377. }
  378. if (is_object($criteria)) {
  379. $criteria = $xpdo->addDerivativeCriteria($className, $criteria);
  380. $row= null;
  381. if ($xpdo->_cacheEnabled && $criteria->cacheFlag && $cacheFlag) {
  382. $row= $xpdo->fromCache($criteria, $className);
  383. }
  384. if ($row === null || !is_array($row)) {
  385. if ($rows= xPDOObject :: _loadRows($xpdo, $className, $criteria)) {
  386. $row= $rows->fetch(PDO::FETCH_ASSOC);
  387. $rows->closeCursor();
  388. }
  389. } else {
  390. $fromCache= true;
  391. }
  392. if (!is_array($row)) {
  393. 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));
  394. } else {
  395. $instance= xPDOObject :: _loadInstance($xpdo, $className, $criteria, $row);
  396. if (is_object($instance)) {
  397. if (!$fromCache && $cacheFlag && $xpdo->_cacheEnabled) {
  398. $xpdo->toCache($criteria, $instance, $cacheFlag);
  399. if ($xpdo->getOption(xPDO::OPT_CACHE_DB_OBJECTS_BY_PK) && ($cacheKey= $instance->getPrimaryKey()) && !$instance->isLazy()) {
  400. $pkCriteria = $xpdo->newQuery($className, $cacheKey, $cacheFlag);
  401. $xpdo->toCache($pkCriteria, $instance, $cacheFlag);
  402. }
  403. }
  404. if ($xpdo->getDebug() === true) $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Loaded object instance: " . print_r($instance->toArray('', true), true));
  405. }
  406. }
  407. } else {
  408. $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'No valid statement could be found in or generated from the given criteria.');
  409. }
  410. } else {
  411. $xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Invalid class specified: ' . $className);
  412. }
  413. return $instance;
  414. }
  415. /**
  416. * Load a collection of xPDOObject instances.
  417. *
  418. * @static
  419. * @param xPDO &$xpdo A valid xPDO instance.
  420. * @param string $className Name of the class.
  421. * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
  422. * @param boolean|integer $cacheFlag Indicates if the objects should be
  423. * cached and optionally, by specifying an integer value, for how many
  424. * seconds.
  425. * @return array An array of xPDOObject instances or an empty array if no instances are loaded.
  426. */
  427. public static function loadCollection(xPDO & $xpdo, $className, $criteria= null, $cacheFlag= true) {
  428. $objCollection= array ();
  429. $fromCache = false;
  430. if (!$className= $xpdo->loadClass($className)) return $objCollection;
  431. $rows= false;
  432. $fromCache= false;
  433. $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
  434. if (!is_object($criteria)) {
  435. $criteria= $xpdo->getCriteria($className, $criteria, $cacheFlag);
  436. }
  437. if (is_object($criteria)) {
  438. $criteria = $xpdo->addDerivativeCriteria($className, $criteria);
  439. }
  440. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
  441. $rows= $xpdo->fromCache($criteria);
  442. $fromCache = (is_array($rows) && !empty($rows));
  443. }
  444. if (!$fromCache && is_object($criteria)) {
  445. $rows= xPDOObject :: _loadRows($xpdo, $className, $criteria);
  446. }
  447. if (is_array ($rows)) {
  448. foreach ($rows as $row) {
  449. xPDOObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
  450. }
  451. } elseif (is_object($rows)) {
  452. $cacheRows = array();
  453. while ($row = $rows->fetch(PDO::FETCH_ASSOC)) {
  454. xPDOObject :: _loadCollectionInstance($xpdo, $objCollection, $className, $criteria, $row, $fromCache, $cacheFlag);
  455. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $cacheRows[] = $row;
  456. }
  457. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag && !$fromCache) $rows =& $cacheRows;
  458. }
  459. if (!$fromCache && $xpdo->_cacheEnabled && $collectionCaching > 0 && $cacheFlag && !empty($rows)) {
  460. $xpdo->toCache($criteria, $rows, $cacheFlag);
  461. }
  462. return $objCollection;
  463. }
  464. /**
  465. * Load a collection of xPDOObject instances and a graph of related objects.
  466. *
  467. * @static
  468. * @param xPDO &$xpdo A valid xPDO instance.
  469. * @param string $className Name of the class.
  470. * @param string|array $graph A related object graph in array or JSON
  471. * format, e.g. array('relationAlias'=>array('subRelationAlias'=>array()))
  472. * or {"relationAlias":{"subRelationAlias":{}}}. Note that the empty arrays
  473. * are necessary in order for the relation to be recognized.
  474. * @param mixed $criteria A valid primary key, criteria array, or xPDOCriteria instance.
  475. * @param boolean|integer $cacheFlag Indicates if the objects should be
  476. * cached and optionally, by specifying an integer value, for how many
  477. * seconds.
  478. * @return array An array of xPDOObject instances or an empty array if no instances are loaded.
  479. */
  480. public static function loadCollectionGraph(xPDO & $xpdo, $className, $graph, $criteria, $cacheFlag) {
  481. $objCollection = array();
  482. if ($query= $xpdo->newQuery($className, $criteria, $cacheFlag)) {
  483. $query = $xpdo->addDerivativeCriteria($className, $query);
  484. $query->bindGraph($graph);
  485. $rows = array();
  486. $fromCache = false;
  487. $collectionCaching = (integer) $xpdo->getOption(xPDO::OPT_CACHE_DB_COLLECTIONS, array(), 1);
  488. if ($collectionCaching > 0 && $xpdo->_cacheEnabled && $cacheFlag) {
  489. $rows= $xpdo->fromCache($query);
  490. $fromCache = !empty($rows);
  491. }
  492. if (!$fromCache) {
  493. $stmt= $query->prepare();
  494. if ($stmt && $stmt->execute()) {
  495. $objCollection= $query->hydrateGraph($stmt, $cacheFlag);
  496. }
  497. } elseif (!empty($rows)) {
  498. $objCollection= $query->hydrateGraph($rows, $cacheFlag);
  499. }
  500. }
  501. return $objCollection;
  502. }
  503. /**
  504. * Get a set of column names from an xPDOObject for use in SQL queries.
  505. *
  506. * @static
  507. * @param xPDO &$xpdo A reference to an initialized xPDO instance.
  508. * @param string $className The class name to get columns from.
  509. * @param string $tableAlias An optional alias for the table in the query.
  510. * @param string $columnPrefix An optional prefix to prepend to each column name.
  511. * @param array $columns An optional array of field names to include or exclude
  512. * (include is default behavior).
  513. * @param boolean $exclude Determines if any specified columns should be included
  514. * or excluded from the set of results.
  515. * @return string A comma-delimited list of the field names for use in a SELECT clause.
  516. */
  517. public static function getSelectColumns(xPDO & $xpdo, $className, $tableAlias= '', $columnPrefix= '', $columns= array (), $exclude= false) {
  518. $columnarray= array ();
  519. $aColumns= $xpdo->getFields($className);
  520. if ($aColumns) {
  521. if (!empty ($tableAlias)) {
  522. $tableAlias= $xpdo->escape($tableAlias);
  523. $tableAlias.= '.';
  524. }
  525. foreach (array_keys($aColumns) as $k) {
  526. if ($exclude && in_array($k, $columns)) {
  527. continue;
  528. }
  529. elseif (empty ($columns)) {
  530. $columnarray[$k]= "{$tableAlias}" . $xpdo->escape($k);
  531. }
  532. elseif ($exclude || in_array($k, $columns)) {
  533. $columnarray[$k]= "{$tableAlias}" . $xpdo->escape($k);
  534. } else {
  535. continue;
  536. }
  537. if (!empty ($columnPrefix)) {
  538. $columnarray[$k]= $columnarray[$k] . " AS " . $xpdo->escape("{$columnPrefix}{$k}");
  539. }
  540. }
  541. }
  542. return implode(', ', $columnarray);
  543. }
  544. /**
  545. * Constructor
  546. *
  547. * Do not call the constructor directly; see {@link xPDO::newObject()}.
  548. *
  549. * All derivatives of xPDOObject must redeclare this method, and must call
  550. * the parent method explicitly before any additional logic is executed, e.g.
  551. *
  552. * <code>
  553. * public function __construct(xPDO & $xpdo) {
  554. * parent :: __construct($xpdo);
  555. * // Any additional constructor tasks here
  556. * }
  557. * </code>
  558. *
  559. * @access public
  560. * @param xPDO &$xpdo A reference to a valid xPDO instance.
  561. * @return xPDOObject
  562. */
  563. public function __construct(xPDO & $xpdo) {
  564. $this->xpdo= & $xpdo;
  565. $this->container= $xpdo->config['dbname'];
  566. $this->_class= get_class($this);
  567. $pos= strrpos($this->_class, '_');
  568. if ($pos !== false && substr($this->_class, $pos + 1) == $xpdo->config['dbtype']) {
  569. $this->_class= substr($this->_class, 0, $pos);
  570. }
  571. $this->_package= $xpdo->getPackage($this->_class);
  572. $this->_alias= $this->_class;
  573. $this->_table= $xpdo->getTableName($this->_class);
  574. $this->_tableMeta= $xpdo->getTableMeta($this->_class);
  575. $this->_fields= $xpdo->getFields($this->_class);
  576. $this->_fieldMeta= $xpdo->getFieldMeta($this->_class);
  577. $this->_aggregates= $xpdo->getAggregates($this->_class);
  578. $this->_composites= $xpdo->getComposites($this->_class);
  579. $classVars= array ();
  580. if ($relatedObjs= array_merge($this->_aggregates, $this->_composites)) {
  581. if ($this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS)) $classVars= get_object_vars($this);
  582. foreach ($relatedObjs as $aAlias => $aMeta) {
  583. if (!array_key_exists($aAlias, $this->_relatedObjects)) {
  584. if ($aMeta['cardinality'] == 'many') {
  585. $this->_relatedObjects[$aAlias]= array ();
  586. }
  587. else {
  588. $this->_relatedObjects[$aAlias]= null;
  589. }
  590. }
  591. if ($this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS) && !array_key_exists($aAlias, $classVars)) {
  592. $this->$aAlias= & $this->_relatedObjects[$aAlias];
  593. $classVars[$aAlias]= 1;
  594. }
  595. }
  596. }
  597. if ($this->getOption(xPDO::OPT_HYDRATE_FIELDS)) {
  598. if (!$this->getOption(xPDO::OPT_HYDRATE_RELATED_OBJECTS)) $classVars= get_object_vars($this);
  599. foreach ($this->_fields as $fldKey => $fldVal) {
  600. if (!array_key_exists($fldKey, $classVars)) {
  601. $this->$fldKey= & $this->_fields[$fldKey];
  602. }
  603. }
  604. }
  605. $this->setDirty();
  606. }
  607. /**
  608. * Get an option value for this instance.
  609. *
  610. * @param string $key The option key to retrieve a value for.
  611. * @param array|null $options An optional array to search for a value in first.
  612. * @param mixed $default A default value to return if no value is found; null is the default.
  613. * @return mixed The value of the option or the provided default if it is not set.
  614. */
  615. public function getOption($key, $options = null, $default = null) {
  616. if (is_array($options) && array_key_exists($key, $options)) {
  617. $value= $options[$key];
  618. } elseif (array_key_exists($key, $this->_options)) {
  619. $value= $this->_options[$key];
  620. } else {
  621. $value= $this->xpdo->getOption($key, null, $default);
  622. }
  623. return $value;
  624. }
  625. /**
  626. * Set an option value for this instance.
  627. *
  628. * @param string $key The option key to set a value for.
  629. * @param mixed $value A value to assign to the option.
  630. */
  631. public function setOption($key, $value) {
  632. $this->_options[$key]= $value;
  633. }
  634. /**
  635. * Set a field value by the field key or name.
  636. *
  637. * @todo Define and implement field validation.
  638. *
  639. * @param string $k The field key or name.
  640. * @param mixed $v The value to set the field to.
  641. * @param string|callable $vType A string indicating the format of the
  642. * provided value parameter, or a callable function that should be used to
  643. * set the field value, overriding the default behavior.
  644. * @return boolean Determines whether the value was set successfully and was
  645. * determined to be dirty (i.e. different from the previous value).
  646. */
  647. public function set($k, $v= null, $vType= '') {
  648. $set= false;
  649. $callback= '';
  650. $callable= !empty($vType) && is_callable($vType, false, $callback) ? true : false;
  651. $oldValue= null;
  652. if (is_string($k) && !empty($k)) {
  653. if (array_key_exists($k, $this->_fieldMeta)) {
  654. $oldValue= $this->_fields[$k];
  655. if (isset ($this->_fieldMeta[$k]['index']) && $this->_fieldMeta[$k]['index'] === 'pk' && isset ($this->_fieldMeta[$k]['generated'])) {
  656. if (!$this->_fieldMeta[$k]['generated'] === 'callback') {
  657. return false;
  658. }
  659. }
  660. if ($callable && $callback) {
  661. $set = $callback($k, $v, $this);
  662. } else {
  663. if (is_string($v) && $this->getOption(xPDO::OPT_ON_SET_STRIPSLASHES)) {
  664. $v= stripslashes($v);
  665. }
  666. if ($oldValue !== $v) {
  667. //type validation
  668. $phptype= $this->_fieldMeta[$k]['phptype'];
  669. $dbtype= $this->_fieldMeta[$k]['dbtype'];
  670. $allowNull= isset($this->_fieldMeta[$k]['null']) ? (boolean) $this->_fieldMeta[$k]['null'] : true;
  671. if ($v === null) {
  672. if ($allowNull) {
  673. $this->_fields[$k]= null;
  674. $set= true;
  675. } else {
  676. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "{$this->_class}: Attempt to set NOT NULL field {$k} to NULL");
  677. }
  678. }
  679. else {
  680. switch ($phptype) {
  681. case 'timestamp' :
  682. case 'datetime' :
  683. $ts= false;
  684. if (preg_match('/int/i', $dbtype)) {
  685. if (strtolower($vType) == 'integer' || is_int($v) || $v == '0') {
  686. $ts= (integer) $v;
  687. } else {
  688. $ts= strtotime($v);
  689. }
  690. if ($ts === false) {
  691. $ts= 0;
  692. }
  693. $this->_fields[$k]= $ts;
  694. $set= true;
  695. } else {
  696. if ($vType == 'utc' || in_array($v, $this->xpdo->driver->_currentTimestamps) || $v === '0000-00-00 00:00:00') {
  697. $this->_fields[$k]= (string) $v;
  698. $set= true;
  699. } else {
  700. if (strtolower($vType) == 'integer' || is_int($v)) {
  701. $ts= intval($v);
  702. } elseif (is_string($v) && !empty($v)) {
  703. $ts= strtotime($v);
  704. }
  705. if ($ts !== false) {
  706. $this->_fields[$k]= strftime('%Y-%m-%d %H:%M:%S', $ts);
  707. $set= true;
  708. }
  709. }
  710. }
  711. break;
  712. case 'date' :
  713. if (preg_match('/int/i', $dbtype)) {
  714. if (strtolower($vType) == 'integer' || is_int($v) || $v == '0') {
  715. $ts= (integer) $v;
  716. } else {
  717. $ts= strtotime($v);
  718. }
  719. if ($ts === false) {
  720. $ts= 0;
  721. }
  722. $this->_fields[$k]= $ts;
  723. $set= true;
  724. } else {
  725. if ($vType == 'utc' || in_array($v, $this->xpdo->driver->_currentDates) || $v === '0000-00-00') {
  726. $this->_fields[$k]= $v;
  727. $set= true;
  728. } else {
  729. if (strtolower($vType) == 'integer' || is_int($v)) {
  730. $ts= intval($v);
  731. } elseif (is_string($v) && !empty($v)) {
  732. $ts= strtotime($v);
  733. }
  734. $ts= strtotime($v);
  735. if ($ts !== false) {
  736. $this->_fields[$k]= strftime('%Y-%m-%d', $ts);
  737. $set= true;
  738. }
  739. }
  740. }
  741. break;
  742. case 'boolean' :
  743. $this->_fields[$k]= intval($v);
  744. $set= true;
  745. break;
  746. case 'integer' :
  747. $this->_fields[$k]= intval($v);
  748. $set= true;
  749. break;
  750. case 'array' :
  751. if (is_object($v) && $v instanceof xPDOObject) {
  752. $v = $v->toArray();
  753. }
  754. if (is_array($v)) {
  755. $this->_fields[$k]= serialize($v);
  756. $set= true;
  757. }
  758. break;
  759. case 'json' :
  760. if (is_object($v) && $v instanceof xPDOObject) {
  761. $v = $v->toArray();
  762. }
  763. if (is_string($v)) {
  764. $v= $this->xpdo->fromJSON($v, true);
  765. }
  766. if (is_array($v)) {
  767. $this->_fields[$k]= $this->xpdo->toJSON($v);
  768. $set= true;
  769. }
  770. break;
  771. default :
  772. $this->_fields[$k]= $v;
  773. $set= true;
  774. }
  775. }
  776. }
  777. }
  778. } elseif ($this->getOption(xPDO::OPT_HYDRATE_ADHOC_FIELDS)) {
  779. $oldValue= isset($this->_fields[$k]) ? $this->_fields[$k] : null;
  780. if ($callable) {
  781. $set = $callback($k, $v, $this);
  782. } else {
  783. $this->_fields[$k]= $v;
  784. $set= true;
  785. }
  786. }
  787. if ($set && $oldValue !== $this->_fields[$k]) {
  788. $this->setDirty($k);
  789. } else {
  790. $set= false;
  791. }
  792. if ($set && $this->getOption(xPDO::OPT_HYDRATE_FIELDS) && !isset ($this->$k)) {
  793. $this->$k= & $this->_fields[$k];
  794. }
  795. } else {
  796. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'xPDOObject - Called set() with an invalid field name: ' . print_r($k, 1));
  797. }
  798. return $set;
  799. }
  800. /**
  801. * Get a field value (or a set of values) by the field key(s) or name(s).
  802. *
  803. * Warning: do not use the $format parameter if retrieving multiple values of
  804. * different types, as the format string will be applied to all types, most
  805. * likely with unpredicatable results. Optionally, you can supply an associate
  806. * array of format strings with the field key as the key for the format array.
  807. *
  808. * @param string|array $k A string (or an array of strings) representing the field
  809. * key or name.
  810. * @param string|array $format An optional variable (or an array of variables) to
  811. * format the return value(s).
  812. * @param mixed $formatTemplate An additional optional variable that can be used in
  813. * formatting the return value(s).
  814. * @return mixed The value(s) of the field(s) requested.
  815. */
  816. public function get($k, $format = null, $formatTemplate= null) {
  817. $value= null;
  818. if (is_array($k)) {
  819. if ($this->isLazy()) {
  820. $this->_loadFieldData($k);
  821. }
  822. foreach ($k as $key) {
  823. if (array_key_exists($key, $this->_fields)) {
  824. if (is_array($format) && isset ($format[$key])) {
  825. $formatTpl= null;
  826. if (is_array ($formatTemplate) && isset ($formatTemplate[$key])) {
  827. $formatTpl= $formatTemplate[$key];
  828. }
  829. $value[$key]= $this->get($key, $format[$key], $formatTpl);
  830. } elseif (!empty ($format) && is_string($format)) {
  831. $value[$key]= $this->get($key, $format, $formatTemplate);
  832. } else {
  833. $value[$key]= $this->get($key);
  834. }
  835. }
  836. }
  837. } elseif (is_string($k) && !empty($k)) {
  838. if (array_key_exists($k, $this->_fields)) {
  839. if ($this->isLazy($k)) {
  840. $this->_loadFieldData($k);
  841. }
  842. $dbType= $this->_getDataType($k);
  843. $fieldType= $this->_getPHPType($k);
  844. $value= $this->_fields[$k];
  845. if ($value !== null) {
  846. switch ($fieldType) {
  847. case 'boolean' :
  848. $value= (boolean) $value;
  849. break;
  850. case 'integer' :
  851. $value= intval($value);
  852. if (is_string($format) && !empty ($format)) {
  853. if (strpos($format, 're:') === 0) {
  854. if (!empty ($formatTemplate) && is_string($formatTemplate)) {
  855. $value= preg_replace(substr($format, 3), $formatTemplate, $value);
  856. }
  857. } else {
  858. $value= sprintf($format, $value);
  859. }
  860. }
  861. break;
  862. case 'float' :
  863. $value= (float) $value;
  864. if (is_string($format) && !empty ($format)) {
  865. if (strpos($format, 're:') === 0) {
  866. if (!empty ($formatTemplate) && is_string($formatTemplate)) {
  867. $value= preg_replace(substr($format, 3), $formatTemplate, $value);
  868. }
  869. } else {
  870. $value= sprintf($format, $value);
  871. }
  872. }
  873. break;
  874. case 'timestamp' :
  875. case 'datetime' :
  876. if (preg_match('/int/i', $dbType)) {
  877. $ts= intval($value);
  878. } elseif (in_array($value, $this->xpdo->driver->_currentTimestamps)) {
  879. $ts= time();
  880. } else {
  881. $ts= strtotime($value);
  882. }
  883. if ($ts !== false && !empty($value)) {
  884. if (is_string($format) && !empty ($format)) {
  885. if (strpos($format, 're:') === 0) {
  886. $value= date('Y-m-d H:M:S', $ts);
  887. if (!empty ($formatTemplate) && is_string($formatTemplate)) {
  888. $value= preg_replace(substr($format, 3), $formatTemplate, $value);
  889. }
  890. } elseif (strpos($format, '%') === false) {
  891. $value= date($format, $ts);
  892. } else {
  893. $value= strftime($format, $ts);
  894. }
  895. } else {
  896. $value= strftime('%Y-%m-%d %H:%M:%S', $ts);
  897. }
  898. }
  899. break;
  900. case 'date' :
  901. if (preg_match('/int/i', $dbType)) {
  902. $ts= intval($value);
  903. } elseif (in_array($value, $this->xpdo->driver->_currentDates)) {
  904. $ts= time();
  905. } else {
  906. $ts= strtotime($value);
  907. }
  908. if ($ts !== false && !empty($value)) {
  909. if (is_string($format) && !empty ($format)) {
  910. if (strpos($format, 're:') === 0) {
  911. $value= strftime('%Y-%m-%d', $ts);
  912. if (!empty ($formatTemplate) && is_string($formatTemplate)) {
  913. $value= preg_replace(substr($format, 3), $formatTemplate, $value);
  914. }
  915. } elseif (strpos($format, '%') === false) {
  916. $value= date($format, $ts);
  917. } elseif ($ts !== false) {
  918. $value= strftime($format, $ts);
  919. }
  920. } else {
  921. $value= strftime('%Y-%m-%d', $ts);
  922. }
  923. }
  924. break;
  925. case 'array' :
  926. if (is_string($value)) {
  927. $value= unserialize($value);
  928. }
  929. break;
  930. case 'json' :
  931. if (is_string($value) && strlen($value) > 1) {
  932. $value= $this->xpdo->fromJSON($value, true);
  933. }
  934. break;
  935. default :
  936. if (is_string($format) && !empty ($format)) {
  937. if (strpos($format, 're:') === 0) {
  938. if (!empty ($formatTemplate) && is_string($formatTemplate)) {
  939. $value= preg_replace(substr($format, 3), $formatTemplate, $value);
  940. }
  941. } else {
  942. $value= sprintf($format, $value);
  943. }
  944. }
  945. break;
  946. }
  947. }
  948. }
  949. }
  950. return $value;
  951. }
  952. /**
  953. * Gets an object related to this instance by a foreign key relationship.
  954. *
  955. * Use this for 1:? (one:zero-or-one) or 1:1 relationships, which you can
  956. * distinguish by setting the nullability of the field representing the
  957. * foreign key.
  958. *
  959. * For all 1:* relationships for this instance, see {@link getMany()}.
  960. *
  961. * @see xPDOObject::getMany()
  962. * @see xPDOObject::addOne()
  963. * @see xPDOObject::addMany()
  964. *
  965. * @param string $alias Alias of the foreign class representing the related
  966. * object.
  967. * @param object $criteria xPDOCriteria object to get the related objects
  968. * @param boolean|integer $cacheFlag Indicates if the object should be
  969. * cached and optionally, by specifying an integer value, for how many
  970. * seconds.
  971. * @return xPDOObject|null The related object or null if no instance exists.
  972. */
  973. public function & getOne($alias, $criteria= null, $cacheFlag= true) {
  974. $object= null;
  975. if ($fkdef= $this->getFKDefinition($alias)) {
  976. $k= $fkdef['local'];
  977. $fk= $fkdef['foreign'];
  978. if (isset ($this->_relatedObjects[$alias])) {
  979. if (is_object($this->_relatedObjects[$alias])) {
  980. $object= & $this->_relatedObjects[$alias];
  981. return $object;
  982. }
  983. }
  984. if ($criteria === null) {
  985. $criteria= array ($fk => $this->get($k));
  986. }
  987. if ($object= $this->xpdo->getObject($fkdef['class'], $criteria, $cacheFlag)) {
  988. $this->_relatedObjects[$alias]= $object;
  989. }
  990. } else {
  991. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Could not getOne: foreign key definition for alias {$alias} not found.");
  992. }
  993. return $object;
  994. }
  995. /**
  996. * Gets a collection of objects related by aggregate or composite relations.
  997. *
  998. * @see xPDOObject::getOne()
  999. * @see xPDOObject::addOne()
  1000. * @see xPDOObject::addMany()
  1001. *
  1002. * @param string $alias Alias of the foreign class representing the related
  1003. * object.
  1004. * @param object $criteria xPDOCriteria object to get the related objects
  1005. * @param boolean|integer $cacheFlag Indicates if the objects should be
  1006. * cached and optionally, by specifying an integer value, for how many
  1007. * seconds.
  1008. * @return array A collection of related objects or an empty array.
  1009. */
  1010. public function & getMany($alias, $criteria= null, $cacheFlag= true) {
  1011. $collection= $this->_getRelatedObjectsByFK($alias, $criteria, $cacheFlag);
  1012. return $collection;
  1013. }
  1014. /**
  1015. * Adds an object related to this instance by a foreign key relationship.
  1016. *
  1017. * @see xPDOObject::getOne()
  1018. * @see xPDOObject::getMany()
  1019. * @see xPDOObject::addMany()
  1020. *
  1021. * @param mixed &$obj A single object to be related to this instance.
  1022. * @param string $alias The relation alias of the related object (only
  1023. * required if more than one relation exists to the same foreign class).
  1024. * @return boolean True if the related object was added to this object.
  1025. */
  1026. public function addOne(& $obj, $alias= '') {
  1027. $added= false;
  1028. if (is_object($obj)) {
  1029. if (empty ($alias)) {
  1030. if ($obj->_alias == $obj->_class) {
  1031. $aliases = $this->_getAliases($obj->_class, 1);
  1032. if (!empty($aliases)) {
  1033. $obj->_alias = reset($aliases);
  1034. }
  1035. }
  1036. $alias= $obj->_alias;
  1037. }
  1038. $fkMeta= $this->getFKDefinition($alias);
  1039. if ($fkMeta && $fkMeta['cardinality'] === 'one') {
  1040. $obj->_alias= $alias;
  1041. $fk= $fkMeta['foreign'];
  1042. $key= $fkMeta['local'];
  1043. $owner= isset ($fkMeta['owner']) ? $fkMeta['owner'] : 'local';
  1044. $kval= $this->get($key);
  1045. $fkval= $obj->get($fk);
  1046. if ($owner == 'local') {
  1047. $obj->set($fk, $kval);
  1048. }
  1049. else {
  1050. $this->set($key, $fkval);
  1051. }
  1052. $this->_relatedObjects[$obj->_alias]= $obj;
  1053. $this->setDirty($key);
  1054. $added= true;
  1055. } else {
  1056. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Foreign key definition for class {$obj->class}, alias {$obj->_alias} not found, or cardinality is not 'one'.");
  1057. }
  1058. } else {
  1059. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Attempt to add a non-object to a relation with alias ({$alias})");
  1060. }
  1061. if (!$added) {
  1062. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, "Could not add related object! " . (is_object($obj) ? print_r($obj->toArray(), true) : ''));
  1063. }
  1064. return $added;
  1065. }
  1066. /**
  1067. * Adds an object or collection of objects related to this class.
  1068. *
  1069. * This method adds an object or collection of objects in a one-to-
  1070. * many foreign key relationship with this object to the internal list of
  1071. * related objects. By adding these related objects, you can cascade
  1072. * {@link xPDOObject::save()}, {@link xPDOObject::remove()}, and other
  1073. * operations based on the type of relationships defined.
  1074. *
  1075. * @see xPDOObject::addOne()
  1076. * @see xPDOObject::getOne()
  1077. * @see xPDOObject::getMany()
  1078. *
  1079. * @param mixed &$obj A single object or collection of objects to be related
  1080. * to this instance via the intersection class.
  1081. * @param string $alias An optional alias, required only for instances where
  1082. * you have mo…

Large files files are truncated, but you can click here to view the full file