Source for file SchemaManager.class.php

Documentation is available at SchemaManager.class.php

  1. <?php
  2.  
  3. require_once HARMONI."dataManager/schema/Schema.class.php";
  4.  
  5. /**
  6. * Responsible for the synchronization of {@link Schema} classes with the database, and the
  7. * creation of new Types.
  8. *
  9. * @package harmoni.datamanager
  10. *
  11. * @copyright Copyright &copy; 2005, Middlebury College
  12. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
  13. *
  14. * @version $Id: SchemaManager.class.php,v 1.32 2007/09/04 20:25:32 adamfranco Exp $
  15. * @author Gabe Schine
  16. */
  17. class SchemaManager {
  18. var $_schemas;
  19. /**
  20. * Constructor.
  21. * @param array $preloadTypes An array containing a number of {@link Schema} type IDs to
  22. * pre-load structure data for. This will avoid queries later on.
  23. */
  24. function SchemaManager( $preloadTypes ) {
  25. $this->_schemas = array();
  26. // talk to the DB
  27. $this->loadTypes($preloadTypes);
  28. $this->_idService = Services::getService("Id");
  29. debug::output("Initialized new SchemaManager with ".$this->numberOfTypes()." types.",DEBUG_SYS4,"DataManager");
  30. }
  31. /**
  32. * Fetches from the DB a list of registered DataSetTypes.
  33. * @return void
  34. * @param array $preloadTypes An array containing a number of {@link Schema} type IDs to
  35. * load structure data for. This will avoid queries later on.
  36. */
  37. function loadTypes($preloadTypes) {
  38. debug::output("Fetching all our known Schemas from the database.",DEBUG_SYS1, "DataManager");
  39. // let's get all our known types
  40. $query = new SelectQuery;
  41. $query->addTable("dm_schema");
  42. $query->addColumn("id","","dm_schema");
  43. $query->addColumn("displayname","","dm_schema");
  44. $query->addColumn("description","","dm_schema");
  45. $query->addColumn("revision","","dm_schema");
  46. $query->addColumn("other_params","","dm_schema");
  47. $query->addWhere("dm_schema.active = 1");
  48. $dbHandler = Services::getService("DatabaseManager");
  49. $result =$dbHandler->query($query,DATAMANAGER_DBID);
  50. if (!$result) throwError(new UnknownDBError("DataManager"));
  51. while ($result->hasMoreRows()) {
  52. $a = $result->getCurrentRow();
  53. $result->advanceRow();
  54. $otherParams = $a['other_params']?unserialize($a['other_params']):null;
  55. $this->_schemas[$a['id']] = new Schema( $a['id'], $a['displayname'], $a['revision'], $a['description'], $otherParams);
  56. $this->_schemas[$a['id']]->setManagerFlag();
  57. debug::output("Found type ID ".$a['id'].", revision ".$a['revision'],DEBUG_SYS2,"DataManager");
  58. unset($type);
  59. }
  60. $result->free();
  61. // now let's preload
  62. if ($preloadTypes) {
  63. $this->loadMultiple($preloadTypes);
  64. }
  65. }
  66. /**
  67. * Will load the data structures for multiple {@link Schema}s.
  68. * @param ref object An array containing the list of types IDs to be loaded.
  69. * @return void
  70. * @access public
  71. */
  72. function loadMultiple($preloadTypes) {
  73. $ids = $preloadTypes;
  74. if (count($ids)) {
  75. // let's do it
  76. $query = new SelectQuery;
  77. $query->addTable("dm_schema_field");
  78. $query->addColumn("id", "id", "dm_schema_field");
  79. $query->addColumn("name", "label", "dm_schema_field");
  80. $query->addColumn("mult", "mult", "dm_schema_field");
  81. $query->addColumn("required", "required", "dm_schema_field");
  82. $query->addColumn("active", "active", "dm_schema_field");
  83. $query->addColumn("fieldtype", "fieldtype", "dm_schema_field");
  84. $query->addColumn("fk_schema", "fk_schema", "dm_schema_field");
  85. $wheres = array();
  86. foreach ($ids as $id) {
  87. $wheres[] = "fk_schema='".addslashes($id)."'";
  88. }
  89. $query->setWhere("(".implode(" OR ",$wheres).")");
  90. // print "<PRE>".MySQL_SQLGenerator::generateSQLQuery($query)."</PRE>";
  91. $dbHandler = Services::getService("DatabaseManager");
  92. $res = $dbHandler->query($query, DATAMANAGER_DBID);
  93. $rows = array();
  94. while ($res->hasMoreRows()) {
  95. $row = $res->getCurrentRow();
  96. $res->advanceRow();
  97. $theID = $row["fk_schema"];
  98. if (!isset($rows[$theID])) $rows[$theID] = array();
  99. $rows[$theID][] = $row;
  100. }
  101. $res->free();
  102. // printpre($rows);
  103. // now distribute the rows among their respective objects
  104. foreach (array_keys($rows) as $id) {
  105. $obj =$this->getSchema($id);
  106. if (!$obj->loaded()) $obj->populate($rows[$id]);
  107. }
  108. }
  109. }
  110. /**
  111. * Returns the number of registered schemas.
  112. * @return integer
  113. */
  114. function numberOfTypes() {
  115. return count($this->_schemas);
  116. }
  117. /**
  118. * Returns a new {@link Schema} object of $type.
  119. * @param string $type A DNS-style unique ID. Example: "edu.middlebury.schemas.person"
  120. * @param string $displayName This Schema's display name.
  121. * @param optional int $revision The revision of this schema, useful for updating data structures. (default=1)
  122. * @param optional string $description A longer description of this Schema.
  123. * @return ref object The new Schema object.
  124. * @access public
  125. */
  126. function createSchema($type, $displayName, $revision=1, $description='') {
  127. $newDef = new Schema( $type, $displayName, $revision, $description );
  128. return $newDef;
  129. }
  130. /**
  131. * Adds a {@link Schema} to the list of registered types, and
  132. * makes sure that it is reference in the database as well.
  133. * @param string $type A DNS-style unique ID. Example: "edu.middlebury.schemas.person"
  134. * @param string $displayName This Schema's display name.
  135. * @param int $revision The revision of this schema, useful for updating data structures. (default=1)
  136. * @param string $description A longer description of this Schema.
  137. * @return ref object The new Schema object.
  138. * @access private
  139. */
  140. function _addSchema($type, $displayName, $revision, $description) {
  141. debug::output("Adding Schema type '".$type."' to database.",DEBUG_SYS1,"DataManager");
  142. if ($this->schemaExists($type)) {
  143. throwError( new Error(
  144. "A Schema for this Type already exists, so the existing one has been returned.",
  145. "DataManager",false));
  146. debug::output("Returning existing Schema for '".$type."'",DEBUG_SYS5, "DataManager");
  147. return $this->_schemas[$id];
  148. }
  149. $query = new InsertQuery;
  150. $query->setTable("dm_schema");
  151. $query->setColumns(array("id","displayname","description", "revision"));
  152. $query->addRowOfValues( array(
  153. "'".addslashes($type)."'",
  154. "'".addslashes($displayName)."'",
  155. "'".addslashes($description)."'",
  156. $revision
  157. ));
  158. $dbHandler = Services::getService("DatabaseManager");
  159. $result =$dbHandler->query($query,DATAMANAGER_DBID);
  160. if (!$result || $result->getNumberOfRows() != 1) {
  161. throwError( new UnknownDBError("DataManager") );
  162. }
  163. $newSchema = new Schema( $type, $displayName, $revision, $description);
  164. $newSchema->setManagerFlag();
  165.  
  166. // add it to our local arrays
  167. $this->_schemas[$type] =$newSchema;
  168. debug::output("Created new Schema object for '".$type."', revision $revision.",DEBUG_SYS5,"DataManager");
  169. return $newSchema;
  170. }
  171. /**
  172. * Returns TRUE/FALSE if we have a Schema for type $type.
  173. * @param string $type A unique type/ID string.
  174. * @return boolean
  175. * @access public
  176. */
  177. function schemaExists($type) {
  178. return (isset($this->_schemas[$type]))?true:false;
  179. }
  180. /**
  181. * Returns the {@link Schema} object corresponding to $id.
  182. * @param integer $id The ID of the definition.
  183. * @return ref object The schema.
  184. * @access public
  185. */
  186. function getSchemaByID($id) {
  187. if (!isset($this->_schemas[$id])) {
  188. $list = array();
  189. foreach ($this->_schemas as $key => $val)
  190. $list[] = $key;
  191. throwError ( new Error(
  192. "Could not find Schema with ID '$id' in (".printpre($list, true).")!","DataManager",true));
  193. }
  194. return $this->_schemas[$id];
  195. }
  196. /**
  197. * Returns an array of all registered {@link Schema} IDs.
  198. * @return array
  199. * @access public
  200. */
  201. function getAllSchemaIDs() {
  202. return array_keys($this->_schemas);
  203. }
  204. /**
  205. * Delete a schema (mark it inactive
  206. *
  207. * @param string $id
  208. * @return void
  209. * @access public
  210. * @since 6/6/06
  211. */
  212. function deleteSchema ($id) {
  213. // update the row in the table for this schema
  214. $query = new UpdateQuery();
  215. $query->setTable("dm_schema");
  216. $query->setWhere("id='".addslashes($id)."'");
  217. $query->setColumns(array("active"));
  218. $query->setValues(array("0"));
  219. $dbHandler= Services::getService("DatabaseManager");
  220. $dbHandler->query($query,DATAMANAGER_DBID);
  221. // Trash the schema object
  222. if (!isset($this->_schemas[$id])) {
  223. $this->_schemas[$id] = null;
  224. unset($this->_schemas[$id]);
  225. }
  226. }
  227. /**
  228. * Passed a {@link Schema}, will make sure that the definition stored in the database
  229. * reflects what is stored in the passed object.
  230. * @param ref object A {@link Schema} object.
  231. * @return boolean Success/failure.
  232. * @access public
  233. */
  234. function synchronize($new) {
  235. ArgumentValidator::validate($new, ExtendsValidatorRule::getRule("Schema"));
  236. $id = $new->getID();
  237. debug::output("Attempting to synchronize Schema type '".$id."' with
  238. the database.",DEBUG_SYS1,"DataManager");
  239. // check if we already have a definition for this type. if we don't, add a new one.
  240. if (!$this->schemaExists($id)) {
  241. $old =$this->_addSchema($id, $new->getDisplayName(), $new->getRevision(), $new->getDescription());
  242. debug::output("Creating new Schema in the database.",DEBUG_SYS3,"DataManager");
  243. } else {
  244. $old =$this->getSchemaByID($id);
  245. // make sure $oldDef has all its data loaded
  246. $old->load();
  247. debug::output("Using database version for synchronization.",DEBUG_SYS3,"DataManger");
  248. }
  249. /*
  250. The synchronization process is not simple.
  251. compare revision numbers, and update them appropriately. do this last.
  252. get all field ids, from both old def (from DB) and new def, store in $ids[]
  253. For each $ids {
  254. if the field doesn't exist, add it to the DB
  255. if the field does exist {
  256. ...in database, but not in new, delete it
  257. ...in both new def and db {
  258. if mult value has changed ...
  259. ... from yes to no, make the change.
  260. ... from no to yes, make the change.
  261. if versionControl has changed ...
  262. ... shouldn't be a problem -- just make the change
  263. if field type has changed ...
  264. ... NOT ALLOWED!
  265. if active flag has changed ...
  266. ... from yes to no, delete the old
  267. ... from no to yes, re-activate the old
  268. if required flag has changed ...
  269. ... from yes to no, ok, update it
  270. ... from no to yes, throw an error. we can't validate all datasets
  271. if display name has changed
  272. ... change it
  273. if desc has changed
  274. ... change it
  275. }
  276. }
  277. }
  278. */
  279. $allLabels = array_unique(array_merge( $new->getAllIDs(true), $old->getAllIDs(true) ));
  280. debug::output("Merged ids: ".implode(", ",$allLabels),DEBUG_SYS5,"DataManager");
  281. foreach ($allLabels as $label) {
  282. // now we're going to go through the logic above in the comment
  283. debug::output("Checking id '$label' ...", DEBUG_SYS5, "DataManager");
  284. // if the field exists in new and not in old, add it to old, and flag it to add to DB
  285. if (!$old->fieldExists($label) && $new->fieldExists($label)) {
  286. $field =$new->getField($label);
  287. $newField =$field->replicate();
  288. $newField->addToDB();
  289. $old->addField($newField);
  290. debug::output("Label '$label' flagged for addition to database.",DEBUG_SYS5,"DataManager");
  291. unset($newField, $field);
  292. continue;
  293. }
  294. // if the field exists in the old but not in the new, flag it for deletion.
  295. if ($old->fieldExists($label) && !$new->fieldExists($label)) {
  296. $field =$old->getField($label);
  297. $field->delete();
  298. debug::output("Label '$label' flagged for deletion from database.",DEBUG_SYS5,"DataManager");
  299. unset($field);
  300. continue;
  301. }
  302. // ok, if we're at this point it means the label is defined in both definitions.
  303. $oldField =$old->getField($label);
  304. $newField =$new->getField($label);
  305. // first let's check if the types match. if they don't, we're going to go psycho
  306. $oldType = $oldField->getType();
  307. $newType = $newField->getType();
  308. if ($newType != $oldType) {
  309. // go PSYCHO!!!!
  310. throwError( new Error(
  311. "While synchronizing Schema '".$this->_type."', it appears that the
  312. field type for id '$label' is different from what we have stored. It is not possible
  313. to synchronize without potential data loss. Aborting.","DataManager",true));
  314. return false;
  315. }
  316. unset($newType,$oldType);
  317.  
  318. // let's check the active flag
  319. $oldActive = $oldField->isActive();
  320. $newActive = $newField->isActive();
  321. if ($oldActive != $newActive) {
  322. if ($oldActive && !$newActive) {
  323. $oldField->delete();
  324. debug::output("ID '$label' is no longer active.", DEBUG_SYS5, "DataManager");
  325. }
  326. if (!$oldActive && $newActive) {
  327. $oldField->setActiveFlag(true);
  328. $oldField->update();
  329. debug::output("ID '$label' is to be reactivated.", DEBUG_SYS5, "DataManager");
  330. }
  331. }
  332.  
  333. // now let's check the mult
  334. $oldMult = $oldField->getMultFlag();
  335. $newMult = $newField->getMultFlag();
  336. if ($oldMult !== $newMult) { // boolean-safe compare
  337. // ok, now, if we're changing from true to false, just go ahead, make the change
  338. if (!$oldMult && $newMult) {
  339. $oldField->setMultFlag(true);
  340. $oldField->update();
  341. debug::output("Label '$label': activating multiple-values.",DEBUG_SYS5,"DataManager");
  342. }
  343. // otherwise, we'll have to do some smart updating
  344. if ($oldMult && !$newMult) {
  345. // now, make the change
  346. $oldField->setMultFlag(false);
  347. $oldField->update();
  348. debug::output("Label '$label': deactivating multiple values, deleted any additional data entries that would conflict with this setting.",DEBUG_SYS5,"DataManager");
  349. }
  350. }
  351. // check the description
  352. $oldDesc = $oldField->getDescription();
  353. $newDesc = $newField->getDescription();
  354. if ($oldDesc != $newDesc) {
  355. $oldField->updateDescription($newDesc);
  356. $oldField->update();
  357. }
  358. // check the display name
  359. $oldName = $oldField->getDisplayName();
  360. $newName = $newField->getDisplayName();
  361. if ($oldName != $newName) {
  362. $oldField->updateDisplayName($newName);
  363. $oldField->update();
  364. }
  365.  
  366. // now let's check the req
  367. $oldReq = $oldField->isRequired();
  368. $newReq = $newField->isRequired();
  369. if ($oldReq !== $newReq) { // boolean-safe compare
  370. // ok, now, if we're changing from true to false, just go ahead, make the change
  371. if (!$oldReq && $newReq) {
  372. $oldField->setRequired(true);
  373. $oldField->update();
  374. }
  375. // otherwise, throw an error!
  376. if ($oldReq && !$newReq) {
  377. throwError( new Error("synchronize() can not change a field's required flag from NO to YES!","DataManager",true));
  378. return false;
  379. }
  380. }
  381. }
  382. // now that we're done syncrhonizing $newDef with $oldDef, let's commit everything to the DB
  383. $old->commitAllFields();
  384. // lastly, update the row in the table for this schema
  385. // change the database.
  386. $query = new UpdateQuery();
  387. $query->setTable("dm_schema");
  388. $query->setWhere("id='".addslashes($old->getID())."'");
  389. $query->setColumns(array("revision", "displayname", "description", "other_params"));
  390. $query->setValues(array($new->getRevision(),
  391. "'".addslashes($old->getDisplayName())."'",
  392. "'".addslashes($old->getDescription())."'",
  393. "'".addslashes(serialize($old->getOtherParameters()))."'"));
  394. $dbHandler= Services::getService("DatabaseManager");
  395. $dbHandler->query($query,DATAMANAGER_DBID);
  396. $old->loaded(true);
  397. debug::output("... synchronization finished.",DEBUG_SYS2,"DataManager");
  398. }
  399.  
  400. /**
  401. * Will return a string containing valid PHP code to generate the schema passed.
  402. * @param ref object $schema The {@link Schema} from which to generate code.
  403. * @param string $varName The name of the variable in the PHP code that will end up containing the {@link Schema} object.
  404. * @return string
  405. * @access public
  406. * @static
  407. */
  408. function generatePHPCode($schema, $varName) {
  409. $v = $varName;
  410. $t = $schema->getID();
  411. $c = '';
  412. $c .= "\$$v = new Schema(";
  413. $c .= "\n\t\"".addslashes($t)."\",";
  414. $c .= "\n\t\"".addslashes($schema->getDisplayName())."\",";
  415. $c .= "\n\t".$schema->getRevision().",";
  416. $c .= "\n\t\"".addslashes($schema->getDescription)."\",";
  417. $c .= "\n);";
  418.  
  419. $c .= "\n\n";
  420.  
  421. // now add the fields
  422. $labels = $schema->getAllIDs(true);
  423. foreach ($labels as $label) {
  424. $f =$schema->getField($label);
  425. $c .= "\$".$v."->addField(\n";
  426. $c .= "\tnew SchemaField(\n";
  427. $c .= "\t\t\"$label\",\n";
  428. $c .= "\t\t\"".addslashes($f->getDisplayName)."\",\n";
  429. $c .= "\t\t\"".$f->getType()."\",\n";
  430. $c .= "\t\t\"".addslashes($f->getDescription())."\",\n";
  431. $c .= "\t\t".($f->getMultFlag()?"true":"false").",\n";
  432. $c .= "\t\t".($f->isRequired()?"true":"false").",\n";
  433. $c .= "\t\t".($f->isActive()?"true":"false")."\n";
  434. $c .= "\t)\n";
  435. $c .= ");\n";
  436. }
  437. return $c;
  438. }
  439. }
  440.  
  441. ?>

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