Source for file RecordManager.class.php

Documentation is available at RecordManager.class.php

  1. <?php
  2.  
  3. require_once HARMONI."dataManager/record/Record.class.php";
  4. require_once HARMONI."dataManager/record/RecordSet.class.php";
  5. require_once HARMONI."dataManager/record/StorableRecordSet.class.php";
  6.  
  7. /**
  8. * The RecordManager handles the creation, tagging and fetching of {@link Record}s from the database.
  9. *
  10. * @package harmoni.datamanager
  11. *
  12. * @copyright Copyright &copy; 2005, Middlebury College
  13. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  14. *
  15. * @version $Id: RecordManager.class.php,v 1.27 2007/09/04 20:25:32 adamfranco Exp $
  16. *
  17. * @author Gabe Schine
  18. */
  19. class RecordManager {
  20. var $_versionConstraint = null;
  21. var $_recordCache;
  22. var $_recordSetCache;
  23. var $_cacheMode;
  24. function RecordManager() {
  25. $this->_recordCache = array();
  26. $this->_recordSetCache = array();
  27. $this->_cacheMode = true;
  28. }
  29.  
  30. /**
  31. * If set to true, records will be cached, otherwise not.
  32. * @param boolean $mode
  33. * @static
  34. * @return void
  35. */
  36. function setCacheMode($mode) {
  37. $mgr = Services::getService("RecordManager");
  38. $mgr->_setCacheMode($mode);
  39. }
  40.  
  41. function _setCacheMode($mode) {
  42. $this->_cacheMode = $mode;
  43. }
  44. /**
  45. * Returns a {@link RecordSet} object associated with the numeric ID.
  46. * @param int $groupID The RecordSet ID.
  47. * @param optional bool $dontLoad If set to TRUE will not attempt to load the RecordSet, only return it if it's already loaded.
  48. * @return ref object OR NULL if not found.
  49. */
  50. function fetchRecordSet($groupID, $dontLoad=false) {
  51. if ($dontLoad) {
  52. return $this->getCachedRecordSet($groupID);
  53. } else {
  54. $this->loadRecordSets(array($groupID));
  55. return $this->getCachedRecordSet($groupID);
  56. }
  57. }
  58. /**
  59. * Returns a cached {@link RecordSet}.
  60. * @param int $id The ID of the RecordSet.
  61. * @access public
  62. * @return ref object
  63. */
  64. function getCachedRecordSet($id)
  65. {
  66. if (isset($this->_recordSetCache[$id])) {
  67. return $this->_recordSetCache[$id];
  68. } else {
  69. $null = null;
  70. return $null;
  71. }
  72. }
  73. /**
  74. * Puts the passed {@link RecordSet} into the internal cache.
  75. * @param ref object $set A {@link RecordSet}.
  76. * @param boolean $force Re-cache even if we already have it cached. (default=no)
  77. * @access public
  78. * @return void
  79. */
  80. function cacheRecordSet($set, $force=false)
  81. {
  82. $id = $set->getID();
  83. if ($force || !isset($this->_recordSetCache[$id])) {
  84. $this->_recordSetCache[$id] =$set;
  85. }
  86. }
  87.  
  88. /**
  89. * Removes a recordset (and all of its records!) from the cache.
  90. * @param int $id The ID of the record set.
  91. * @access public
  92. * @return void
  93. */
  94. function uncacheRecordSet($id) {
  95. $set =$this->getCachedRecordSet($id);
  96. if ($set) {
  97. foreach($set->getRecordIDs() as $i) {
  98. $this->uncacheRecord($i);
  99. }
  100. }
  101. unset($this->_recordSetCache[$id]);
  102. }
  103.  
  104. /**
  105. * Removes a record from the cache.
  106. * @param int $id The ID of the record.
  107. * @access public
  108. * @return void
  109. */
  110. function uncacheRecord($id) {
  111. unset($this->_recordCache[$id]);
  112. }
  113. /**
  114. * Returns the Ids of all groups a Record is in.
  115. *
  116. * @param ref object $record The {@link Record}.
  117. * @return array An indexed array of the group ids (integers).
  118. */
  119. function getRecordSetIDsContaining($record) {
  120. return $this->getRecordSetIDsContainingID($record->getID());
  121. }
  122. /**
  123. * Returns the Ids of all groups a Record ID is in.
  124. *
  125. * @param int $id
  126. * @return array An indexed array of the group ids (integers).
  127. */
  128. function getRecordSetIDsContainingID($id) {
  129. if (!$id) return array(); // no ID
  130. $query = new SelectQuery;
  131. $query->addTable("dm_record_set");
  132. $query->addColumn("id");
  133. $query->addWhere("fk_record='".addslashes($id)."'");
  134. $dbHandler = Services::getService("DatabaseManager");
  135. $result = $dbHandler->query($query,DATAMANAGER_DBID);
  136. $groupIds = array();
  137. while ($result->hasMoreRows()) {
  138. $groupIds[] = $result->field("id");
  139. $result->advanceRow();
  140. }
  141. $result->free();
  142. return $groupIds;
  143. }
  144. /**
  145. * Loads the specified {@link RecordSet}s into the cache.
  146. * @param array $groupIDsArray An array of numeric IDs.
  147. * @return void
  148. */
  149. function loadRecordSets($groupIDsArray) {
  150. $fromDBIDs = array();
  151.  
  152. foreach ($groupIDsArray as $id) {
  153. if (!$this->getCachedRecordSet($id))
  154. $fromDBIDs[] = $id;
  155. }
  156.  
  157. if (count($fromDBIDs)) {
  158. $wheres = array();
  159. foreach ($fromDBIDs as $id) {
  160. $wheres[] = "dm_record_set.id='".addslashes($id)."'";
  161. }
  162. $query = new SelectQuery;
  163. $query->addTable("dm_record_set");
  164. $query->addTable("dm_record", LEFT_JOIN, "dm_record_set.fk_record=dm_record.id");
  165. $query->addColumn("id","","dm_record_set");
  166. $query->addColumn("fk_record","","dm_record_set");
  167. $query->setWhere(implode(" OR ",$wheres));
  168. $dbHandler = Services::getService("DatabaseManager");
  169. $result = $dbHandler->query($query,DATAMANAGER_DBID);
  170.  
  171. while ($result->hasMoreRows()) {
  172. $a = $result->getCurrentRow();
  173. $result->advanceRow();
  174. $id = $a["id"];
  175. $newSet =$this->getCachedRecordSet($id);
  176. if (!$newSet) {
  177. $newSet = new StorableRecordSet($id);
  178. $this->cacheRecordSet($newSet);
  179. }
  180. $newSet->takeRow($a);
  181. }
  182. $result->free();
  183. }
  184. // now, if some of the IDs didn't exist in the DB, we'll create new ones.
  185. foreach ($groupIDsArray as $id) {
  186. if (!$this->getCachedRecordSet($id)) {
  187. $newSet = new StorableRecordSet($id);
  188. $this->cacheRecordSet($newSet);
  189. }
  190. }
  191. }
  192.  
  193. /**
  194. * Pre-loads all of the records which are contained in the RecordSet IDs passed.
  195. * This is useful to speed up fetching records for multiple RecordSets
  196. * @return void
  197. * @param array $IDs An array of RecordSet ids.
  198. * @param optional int $fetchMode the fetchmode to get the records (one of RECORD_*).
  199. * @access public
  200. */
  201. function preCacheRecordsFromRecordSetIDs($ids, $fetchMode=RECORD_CURRENT) {
  202. $this->loadRecordSets($ids);
  203. $recordIDs = array();
  204. foreach ($ids as $id) {
  205. $set =$this->fetchRecordSet($id);
  206. $temp = $set->getRecordIDs();
  207. $recordIDs = array_merge($temp, $recordIDs);
  208. }
  209. $recordIDs = array_unique($recordIDs);
  210.  
  211. $this->fetchRecords($recordIDs, $fetchMode);
  212. }
  213. /**
  214. * Fetches and returns an array of Record IDs from the database in one Query.
  215. * @return ref array Indexed by Record ID, values are {@link Record}s.
  216. * @param array $IDs
  217. * @param optional int $mode Specifies the mode the record should be fetched.
  218. * @param optional object $limitResults NOT YET IMPLEMENTED
  219. * criteria. If not specified, will fetch all IDs.
  220. */
  221. function fetchRecords( $IDs, $mode = RECORD_CURRENT, $limitResults = null ) {
  222. ArgumentValidator::validate($IDs, ArrayValidatorRuleWithRule::getRule(OrValidatorRule::getRule(StringValidatorRule::getRule(), IntegerValidatorRule::getRule())));
  223. ArgumentValidator::validate($mode, IntegerValidatorRule::getRule());
  224. $IDs = array_unique($IDs);
  225.  
  226. // let's weed out those IDs that we can take from cache
  227. $fromCacheIDs = array();
  228. $fromDBIDs = array();
  229. if (count($this->_recordCache)) {
  230. foreach ($IDs as $id) {
  231. // only take from the cache if we have it cached, AND
  232. // what is cached is fetched at a higher/equal data-mode than what's requested
  233. if (isset($this->_recordCache[$id]) &&
  234. $this->_recordCache[$id]->getFetchMode()>=$mode) {
  235. $fromCacheIDs[] = $id;
  236. } else {
  237. $fromDBIDs[] = $id;
  238. }
  239. }
  240. } else {
  241. $fromDBIDs = $IDs;
  242. }
  243. // print_r($fromCacheIDs);
  244. // print_r($fromDBIDs);
  245. // printDebugBacktrace();
  246. $records = array();
  247.  
  248. // put all the records from the cache into the array
  249. foreach ($IDs as $id) {
  250. if (isset($this->_recordCache[$id])) $records[$id] =$this->_recordCache[$id];
  251. }
  252. if (count($fromDBIDs)) {
  253. // first, make the new query
  254. $query = new SelectQuery();
  255. $this->_setupSelectQuery($query, $mode);
  256. // and now, go through the records we already have cached but are fetching more information for,
  257. // and make sure that we don't fetch information for fields that are already fetched.
  258. $alreadyFetchedFields = array();
  259. // and, build the WHERE clause while we're at it.
  260. $t = array();
  261. foreach ($fromDBIDs as $id) {
  262. $t[] = "dm_record.id='".addslashes($id)."'";
  263. if (isset($this->_recordCache[$id])) {
  264. $alreadyFetchedFields = array_unique(array_merge($alreadyFetchedFields, $this->_recordCache[$id]->getFetchedFieldIDs()));
  265. }
  266. }
  267.  
  268. $query->addWhere("(".implode(" OR ", $t).")");
  269. if (count($alreadyFetchedFields)) {
  270. $temp = array();
  271. foreach ($alreadyFetchedFields as $id) {
  272. $temp[] = "dm_record_field.id != '".addslashes($id)."'";
  273. }
  274. $query->addWhere('(' . implode(" AND ", $temp) . ')');
  275. }
  276. $dbHandler = Services::getService("DatabaseManager");
  277. // print "<PRE>" . MySQL_SQLGenerator::generateSQLQuery($query)."</PRE>";
  278. $result =$dbHandler->query($query,DATAMANAGER_DBID);
  279. if (!$result) {
  280. throwError(new UnknownDBError("RecordManager"));
  281. }
  282. // now, we need to parse things out and distribute the lines accordingly
  283. while ($result->hasMoreRows()) {
  284. $a = $result->getCurrentRow();
  285. $result->advanceRow();
  286. $id = $a['record_id'];
  287. $type = $a['fk_schema'];
  288. $vcontrol =$a['record_ver_control'];
  289. if (!isset($records[$id])) {
  290. $schemaManager = Services::getService("SchemaManager");
  291. $schema =$schemaManager->getSchemaByID($type);
  292. $schema->load();
  293. $records[$id] = new Record($schema, $vcontrol?true:false, $mode);
  294. if ($this->_cacheMode) $this->_recordCache[$id] =$records[$id];
  295. }
  296. $records[$id]->takeRow($a);
  297. unset($a);
  298. }
  299. $result->free();
  300. }
  301.  
  302. // make sure we found the data sets
  303. $rule = ExtendsValidatorRule::getRule("Record");
  304. foreach ($IDs as $id) {
  305. if (!$rule->check($records[$id]))
  306. throwError(new Error(UNKNOWN_ID.": Record $id was requested, but not found.", "DataManager", TRUE));
  307. // and set the fetch mode.
  308. $records[$id]->setFetchMode($mode);
  309. // print "<pre>";print_r($records[$id]);print "</pre>";
  310. }
  311. return $records;
  312. }
  313. /**
  314. * Takes an array of IDs and some search criteria, and weeds out the IDs that don't
  315. * match that criteria.
  316. * @param ref object $criteria The {@link SearchCriteria}.
  317. * @param optional array $ids An array of Record IDs to search among. If not specified, all records will be searched.
  318. * @access public
  319. */
  320. function getRecordIDsBySearch($criteria, $ids=null) {
  321. // this should happen in one query.
  322. // the WHERE clause of the SQL query will be relatively complicated.
  323. $query = new SelectQuery();
  324. $this->_setupSelectQuery($query, RECORD_CURRENT);
  325. $searchString = $criteria->returnSearchString();
  326. if ($ids) {
  327. $parts1 = array();
  328. foreach ($ids as $id) {
  329. $parts1[] = "'".addslashes($id)."'";
  330. }
  331. $part1 = "dm_record.id IN (".implode(", ", $parts1).")";
  332. }
  333. $part2 = $searchString;
  334. $fullWhere = (isset($part1)?"($part1) AND ":"")."($part2)";
  335. $query->setWhere($fullWhere);
  336. // print "<PRE>". MySQL_SQLGenerator::generateSQLQuery($query)."</PRE>";
  337. $dbHandler = Services::getService("DatabaseManager");
  338. $result =$dbHandler->query($query, DATAMANAGER_DBID);
  339. $resultIds = array();
  340. while ($result->hasMoreRows()) {
  341. $a = $result->getCurrentRow();
  342. $result->advanceRow();
  343. $resultIds[] = $a["record_id"];
  344. }
  345. $result->free();
  346. $ids = $criteria->postProcess($resultIds);
  347. return array_unique($ids);
  348. }
  349. /**
  350. * Takes an array of record set IDs and some search criteria, and weeds out
  351. * the IDs that don't match that criteria.
  352. * @param ref object $criteria The {@link SearchCriteria}.
  353. * @param optional array $ids An array of RecordSet IDs to search among.
  354. * If not specified, all records will be searched.
  355. * @return array
  356. * @access public
  357. */
  358. function getRecordSetIDsBySearch($criteria, $ids=null) {
  359. // this should happen in one query.
  360. // the WHERE clause of the SQL query will be relatively complicated.
  361. $query = new SelectQuery();
  362. $query->addColumn("id","record_set_id","dm_record_set");
  363. $query->addTable("dm_record");
  364. $query->addTable("dm_record_field",LEFT_JOIN,"(dm_record_field.fk_record=dm_record.id AND dm_record_field.active=1)");
  365. $query->addTable("dm_schema_field",LEFT_JOIN,"dm_record_field.fk_schema_field=dm_schema_field.id");
  366. $query->addTable("dm_record_set", INNER_JOIN, "dm_record_set.fk_record = dm_record.id");
  367. $dataTypeManager = Services::getService("DataTypeManager");
  368. $list = $dataTypeManager->getRegisteredStorablePrimitives();
  369.  
  370. foreach ($list as $type) {
  371. eval("$type::alterQuery(\$query);");
  372. }
  373. $searchString = $criteria->returnSearchString();
  374. if ($ids) {
  375. $parts1 = array();
  376. foreach ($ids as $id) {
  377. $parts1[] = "'".addslashes($id)."'";
  378. }
  379. $part1 = "\n\tdm_record_set.id IN (\n\t\t".implode(",\n\t\t", $parts1).")";
  380. }
  381. $part2 = $searchString;
  382. $fullWhere = (isset($part1)?"($part1) \n\tAND ":"")."($part2)";
  383. $query->setWhere($fullWhere);
  384. // print "<PRE>". MySQL_SQLGenerator::generateSQLQuery($query)."</PRE>";
  385. $dbHandler = Services::getService("DatabaseManager");
  386. $result =$dbHandler->query($query, DATAMANAGER_DBID);
  387. $resultIds = array();
  388. while ($result->hasMoreRows()) {
  389. $a = $result->getCurrentRow();
  390. $result->advanceRow();
  391. $resultIds[] = $a["record_set_id"];
  392. }
  393. $result->free();
  394. $ids = $criteria->postProcess($resultIds);
  395. return array_unique($ids);
  396. }
  397. /**
  398. * Fetches a single Record from the database.
  399. * @return ref object
  400. * @param int $id
  401. * @param optional int $mode
  402. */
  403. function fetchRecord( $id, $mode=RECORD_CURRENT ) {
  404. $records =$this->fetchRecords(array($id), $mode);
  405. return $records[$id];
  406. }
  407. /**
  408. * Deletes the Record of the Specified Id
  409. * @param int $id
  410. * @param optional bool $prune Set to TRUE if you want the Record to actually be pruned from the database and not just deactivated.
  411. */
  412. function deleteRecord ( $id, $prune=false ) {
  413. $mode = RECORD_FULL;
  414. $record =$this->fetchRecord( $id, $mode );
  415. $record->delete();
  416. $record->commit();
  417. }
  418. /**
  419. * Delete the Record Set and any records that are referenced only by this record
  420. * set and not shared with other record sets.
  421. *
  422. * @param int $id The Id of the set to delete.
  423. * @param optional boolean $prune If TRUE will make sure that the Records are removed
  424. * from the database.
  425. * @return void
  426. * @access public
  427. * @since 10/6/04
  428. */
  429. function deleteRecordSet ($id, $prune = false) {
  430. ArgumentValidator::validate($id, StringValidatorRule::getRule());
  431. $recordSet =$this->fetchRecordSet($id);
  432. $recordSet->loadRecords($prune?RECORD_FULL:RECORD_NODATA);
  433. // Delete the records in the set.
  434. $records =$recordSet->getRecords();
  435. foreach (array_keys($records) as $key) {
  436. $record =$records[$key];
  437. // Only delete records if they are not shared with other sets.
  438. $setsContaining = $this->getRecordSetIDsContaining($record);
  439. if (count($setsContaining) == 1
  440. && $setsContaing[0] == $id)
  441. {
  442. $this->deleteRecord($record->getID(), $prune);
  443. }
  444. }
  445. // Delete the set from the database
  446. $query = new DeleteQuery;
  447. $query->setTable("dm_record_set");
  448. $query->addWhere("id = '".addslashes($id)."'");
  449. $dbHandler = Services::getService("DatabaseManager");
  450. $result =$dbHandler->query($query,DATAMANAGER_DBID);
  451. $this->_recordSetCache[$id] = NULL;
  452. unset($this->_recordSetCache[$id]);
  453. }
  454. /**
  455. * Initializes a SelectQuery with the complex JOIN structures of the DataManager.
  456. * @return void
  457. * @param ref object $query
  458. * @param optional int $mode Specifies the mode we are fetching our results. Must be one of RESULT_* constants.
  459. * @access private
  460. */
  461. function _setupSelectQuery($query, $mode=RECORD_CURRENT) {
  462. // this function sets up the selectquery to include all the necessary tables
  463. $query->addTable("dm_record");
  464. if ($mode > RECORD_NODATA) {
  465. if ($mode < RECORD_FULL) {
  466. $query->addTable("dm_record_field",LEFT_JOIN,"(dm_record_field.fk_record=dm_record.id AND dm_record_field.active=1)");
  467. } else {
  468. $query->addTable("dm_record_field",LEFT_JOIN,"dm_record_field.fk_record=dm_record.id");
  469. }
  470. $query->addTable("dm_schema_field",LEFT_JOIN,"dm_record_field.fk_schema_field=dm_schema_field.id");
  471. $dataTypeManager = Services::getService("DataTypeManager");
  472. $list = $dataTypeManager->getRegisteredStorablePrimitives();
  473.  
  474. foreach ($list as $type) {
  475. eval("$type::alterQuery(\$query);");
  476. }
  477. /* dm_record_field table */
  478. $query->addColumn("id","record_field_id","dm_record_field");
  479. $query->addColumn("value_index","record_field_index","dm_record_field");
  480. $query->addColumn("active","record_field_active","dm_record_field");
  481. $query->addColumn("modified","record_field_modified","dm_record_field");
  482. $query->addColumn("fk_data");
  483. /* dm_schema_field table */
  484. $query->addColumn("id","schema_field_id","dm_schema_field");
  485. // $query->addColumn("label","schema_field_label","dm_schema_field");
  486. }
  487. /* dm_record table */
  488. $query->addColumn("id","record_id","dm_record");
  489. $query->addColumn("created","record_created","dm_record");
  490. $query->addColumn("ver_control","record_ver_control","dm_record");
  491. $query->addColumn("fk_schema","","dm_record"); //specify table to avoid ambiguity
  492. }
  493. /**
  494. * Returns a new {@link Record} object that can be inserted into the database.
  495. * @return ref object
  496. * @param string $type The Schema type/ID that refers to the Schema to associate this Record with.
  497. * @param optional bool $verControl Specifies if the Record should be created with Version Control. Default=no.
  498. */
  499. function createRecord( $type, $verControl = false ) {
  500. $schemaManager = Services::getService("SchemaManager");
  501. if (!$schemaManager->schemaExists($type)) {
  502. throwError ( new Error("could not create new Record of type ".$type.
  503. " because the requested type does not seem to be registered
  504. with the SchemaManager.","RecordManager",true));
  505. }
  506. // ok, let's make a new one.
  507. $schema =$schemaManager->getSchemaByID($type);
  508. // load from the DB
  509. $schema->load();
  510. debug::output("Creating new Record of type '".$type."', which allows fields: ".implode(", ",$schema->getAllIDs()),DEBUG_SYS4,"DataManager");
  511. $newRecord = new Record($schema, $verControl);
  512. return $newRecord;
  513. }
  514. /**
  515. * @return array
  516. * @param string $type The Schema type to look for.
  517. * Returns an array of Record IDs that are of the Schema type $type.
  518. */
  519. function getRecordIDsByType($type) {
  520. // we're going to get all the IDs that match a given type.
  521. $query = new SelectQuery();
  522. $query->addTable("dm_record");
  523. $query->addTable("dm_schema",INNER_JOIN,"dm_schema.id=dm_record.fk_schema");
  524. $query->addColumn("id","","dm_record");
  525. $query->setWhere("dm_schema.id='".addslashes($type)."'");
  526. $dbHandler = Services::getService("DatabaseManager");
  527. $result =$dbHandler->query($query,DATAMANAGER_DBID);
  528. if (!$result) {
  529. throwError( new UnknownDBError("RecordManager") );
  530. return false;
  531. }
  532. $array = array();
  533. while ($result->hasMoreRows()) {
  534. $array[] = $result->field(0);
  535. $result->advanceRow();
  536. }
  537. $result->free();
  538. return $array;
  539. }
  540. }
  541.  
  542. ?>

Documentation generated on Wed, 19 Sep 2007 10:25:53 -0400 by phpDocumentor 1.3.0RC3