ChadoRecord.inc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. class ChadoRecord {
  3. /**
  4. * @var string
  5. * Holds the name of the table that this record belogns to.
  6. */
  7. protected $table_name = '';
  8. /**
  9. * @var array
  10. * Holds the Drupal schema definition for this table.
  11. */
  12. protected $schema = [];
  13. /**
  14. * @var array
  15. * Holds the values for the columns of the record
  16. */
  17. protected $values = [];
  18. /**
  19. * @var array
  20. * An array of required columns.
  21. */
  22. protected $required_cols = [];
  23. /**
  24. * @var integer
  25. * The numeric Id for this record.
  26. */
  27. protected $record_id = NULL;
  28. /**
  29. * @var string
  30. * The column name for the primary key.
  31. */
  32. protected $pkey = '';
  33. /**
  34. * The list of column names in the table.
  35. * @var array
  36. */
  37. protected $column_names = [];
  38. /**
  39. * The ChadoRecord constructor
  40. *
  41. * @param string $table_name
  42. * The name of the table that the record belongs to.
  43. */
  44. public function __construct($table_name, $record_id = NULL) {
  45. if (!$table_name) {
  46. $message = t('ChadoRecord::_construct(). The $table_name argument is required for a ChadoRecord instance.');
  47. throw new Exception($message);
  48. }
  49. // Set the table name and schema.
  50. $this->table_name = $table_name;
  51. $this->schema = chado_get_schema($this->table_name);
  52. if (!$this->schema) {
  53. $message = t('ChadoRecord::_construct(). Could not find a matching table schema in Chado for the table: !table.',
  54. ['!table' => $this->table_name]);
  55. throw new Exception($message);
  56. }
  57. // Chado tables never have more than one column as a primary key so
  58. // we are good just getting the first element.
  59. $this->pkey = $this->schema['primary key'][0];
  60. // Save the column names.
  61. foreach ($this->schema['fields'] as $column_name => $col_details) {
  62. $this->column_names[] = $column_name;
  63. }
  64. // Get the required columns.
  65. foreach ($this->schema['fields'] as $column => $col_schema) {
  66. foreach ($col_schema as $param => $val) {
  67. if (preg_match('/not null/i', $param) and $col_schema[$param]) {
  68. $this->required_cols[] = $column;
  69. }
  70. }
  71. }
  72. // If a record_id was provided then lookup the record and set the values.
  73. if ($record_id) {
  74. try {
  75. $sql = 'SELECT * FROM {' . $this->table_name . '} WHERE ' . $this->pkey . ' = :record_id';
  76. $result = chado_query($sql, [':record_id' => $record_id]);
  77. $values = $result->fetchAssoc();
  78. if (empty($values)) {
  79. $message = t('ChadoRecord::_construct(). Could not find a record in table, !table, with the given !pkey: !record_id.',
  80. ['!pkey' => $this->pkey, '!record_id' => $record_id, '!table' => $this->table_name]);
  81. throw new Exception($message);
  82. }
  83. $this->record_id = $record_id;
  84. $this->values = $values;
  85. }
  86. catch (Exception $e) {
  87. $message = t('ChadoRecord::_construct(). Could not find a record in table, !table, with the given !pkey: !record_id. ERROR: !error',
  88. ['!pkey' => $this->pkey, '!record_id' => $record_id, '!table' => $this->table_name, '!error' => $e->getMessage()]);
  89. throw new Exception($message);
  90. }
  91. }
  92. }
  93. /**
  94. * Retrieves the record ID.
  95. *
  96. * @return number
  97. */
  98. public function getID() {
  99. return $this->record_id;
  100. }
  101. /**
  102. * Retrieves the table name.
  103. *
  104. * @return string
  105. * The name of the table that the record belongs to.
  106. */
  107. public function getTable() {
  108. return $this->table;
  109. }
  110. /**
  111. * Retrieves the table schema.
  112. *
  113. * @return array
  114. * The Drupal schema array for the table.
  115. */
  116. public function getSchema() {
  117. return $this->schema;
  118. }
  119. /**
  120. * Creates a clone of this record, but without the record ID.
  121. *
  122. * @return ChadoRecord
  123. * Returns a new ChadoRecord but with all of the same values but
  124. * without the record ID.
  125. */
  126. public function clone() {
  127. $clone = new ChadoRecord($this->table_name);
  128. $clone_values = $this->values;
  129. unset($clone_values[$this->pkey]);
  130. $clone->setValues($clone_values);
  131. return $clone;
  132. }
  133. /**
  134. * Performs either an update or insert into the table using the values.
  135. *
  136. * If the record already exists it will be updated. If the record does not
  137. * exist it will be inserted. This function adds a bit more overhead by
  138. * checking for the existance of the record and performing the appropriate
  139. * action. You can save time by using the insert or update functions directly
  140. * if you only need to do one of those actions specifically.
  141. *
  142. * @throws Exception
  143. */
  144. public function save() {
  145. // Determine if we need to perform an update or an insert.
  146. $num_matches = $this->find();
  147. if ($num_matches == 1) {
  148. $this->update();
  149. }
  150. if ($num_matches == 0) {
  151. $this->insert();
  152. }
  153. if ($num_matches > 1) {
  154. $message = t('ChadoRecord::save(). Could not save the record into the table, !table. '.
  155. 'Multiple records already exist that match the values: !values. '.
  156. 'Please provide a set of values that can uniquely identify a record.',
  157. ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
  158. throw new Exception($message);
  159. }
  160. }
  161. /**
  162. * Inserts the values of this object as a new record.
  163. *
  164. * @throws Exception
  165. */
  166. public function insert() {
  167. // Make sure we have values for this record before inserting.
  168. if (empty($this->values)) {
  169. $message = t('ChadoRecord::insert(). Could not insert a record into the table, !table, without any values.',
  170. ['!table' => $this->table_name]);
  171. throw new Exception($message);
  172. }
  173. // Build the SQL statement for insertion.
  174. $insert_cols = [];
  175. $insert_vals = [];
  176. $insert_args = [];
  177. foreach ($this->values as $column => $value) {
  178. $insert_cols[] = $column;
  179. $insert_vals[] = ':' . $column;
  180. $insert_args[':' . $column] = $value;
  181. }
  182. $sql = 'INSERT INTO {' . $this->table_name . '} (' .
  183. implode(", ", $insert_cols) . ') VALUES (' .
  184. implode(", ", $insert_vals) . ')';
  185. try {
  186. chado_query($sql, $insert_args);
  187. // TODO: we can speed up inserts if we can find a way to not have to
  188. // run the find(), but get the newly inserted record_id directly
  189. // from the insert command.
  190. $this->find();
  191. }
  192. catch (Exception $e) {
  193. $message = t('ChadoRecord::insert(). Could not insert a record into the table, !table, with the following values: !values. ERROR: !error',
  194. ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
  195. throw new Exception($message);
  196. }
  197. }
  198. /**
  199. * Updates the values of this object as a new record.
  200. *
  201. * @throws Exception
  202. */
  203. public function update() {
  204. // Make sure we have values for this record before updating.
  205. if (empty($this->values)) {
  206. $message = t('ChadoRecord::update(). Could not update a record into the table, !table, without any values.',
  207. ['!table' => $this->table_name]);
  208. throw new Exception($message);
  209. }
  210. // We have to have a record ID for the record to update.
  211. if (!$this->record_id) {
  212. $message = t('ChadoRecord::update(). Could not update a record in the table, !table, without a record ID.',
  213. ['!table' => $this->table_name]);
  214. throw new Exception($message);
  215. }
  216. // Build the SQL statement for updating.
  217. $update_args = [];
  218. $sql = 'UPDATE {' . $this->table_name . '} SET ';
  219. foreach ($this->values as $column => $value) {
  220. // We're not updating the primary key so skip that if it's in the values.
  221. if ($column == $this->pkey) {
  222. continue;
  223. }
  224. $sql .= $column . ' = :' . $column . ', ';
  225. $update_args[':' . $column] = $value;
  226. }
  227. // Remove the trailing comma and space.
  228. $sql = substr($sql, 0, -2);
  229. $sql .= ' WHERE ' . $this->pkey . ' = :record_id';
  230. $update_args[':record_id'] = $this->record_id;
  231. // Now try the update.
  232. try {
  233. chado_query($sql, $update_args);
  234. }
  235. catch (Exception $e) {
  236. $message = t('ChadoRecord::update(). Could not update a record in the table, !table, with !record_id as the record ID and the following values: !values. ERROR: !error',
  237. ['!table' => $this->table_name,
  238. '!record_id' => $this->record_id,
  239. '!values' => print_r($this->values, TRUE),
  240. '!error' => $e->getMessage()]);
  241. throw new Exception($message);
  242. }
  243. }
  244. /**
  245. * Deletes the record that matches the given values.
  246. *
  247. * A record ID must be part of the current values.
  248. *
  249. * @throws Exception
  250. */
  251. public function delete() {
  252. // We have to have a record ID for the record to be deleted.
  253. if (!$this->record_id) {
  254. $message = t('ChadoRecord::delete(). Could not delete a record in the table, !table, without a record ID.',
  255. ['!table' => $this->table_name]);
  256. throw new Exception($message);
  257. }
  258. try {
  259. $sql = 'DELETE FROM {' . $this->table_name . '} WHERE ' . $this->pkey . ' = :record_id';
  260. chado_query($sql, [':record_id' => $this->record_id]);
  261. }
  262. catch (Exception $e) {
  263. $message = t('ChadoRecord::delete(). Could not delete a record in the table, !table, with !record_id as the record ID. ERROR: !error',
  264. ['!table' => $this->table_name,
  265. '!record_id' => $this->record_id,
  266. '!error' => $e->getMessage()]);
  267. throw new Exception($message);
  268. }
  269. }
  270. /**
  271. * A general-purpose setter function to set the column values for the record.
  272. *
  273. * This function should be used prior to insert or update of a record. For
  274. * an update, be sure to include the record ID in the list of values passed
  275. * to the function.
  276. *
  277. * @param array $values
  278. * An associative array where the keys are the table column names and
  279. * the values are the record values for each column.
  280. *
  281. * @throws Exception
  282. */
  283. public function setValues($values) {
  284. // Intiailze the values array.
  285. $this->values = [];
  286. // Add the values provided into the values property.
  287. foreach ($values as $column => $value) {
  288. if (in_array($column, $this->column_names)) {
  289. $this->values[$column] = $value;
  290. }
  291. else {
  292. $message = t('ChadoRecord::setValues(). The column named, "!column", does not exist in table: "!table". Values: !values".',
  293. ['!column' => $column, '!table' => $this->table_name, '!values' => print_r($values, TRUE)]);
  294. throw new Exception($message);
  295. }
  296. }
  297. // Make sure that the user did not miss any required columns or has
  298. // set a column to be NULL when it doesn't allow NULLs.
  299. foreach ($this->required_cols as $rcol) {
  300. // It's okay if the primary key is missing, esepecially if the user
  301. // wants to use the find() or insert() functions.
  302. if ($rcol == $this->pkey) {
  303. continue;
  304. }
  305. if (in_array($rcol, array_keys($this->values)) and $this->values[$rcol] === '__NULL__') {
  306. $message = t('ChadoRecord::setValues(). The column named, "!column", requires a value for the table: "!table".',
  307. ['!column' => $rcol, '!table' => $this->table_name]);
  308. throw new Exception($message);
  309. }
  310. }
  311. // Check to see if the user provided the primary key (record_id).
  312. if (in_array($this->pkey, array_keys($values))) {
  313. $this->record_id = $values[$this->pkey];
  314. }
  315. }
  316. /**
  317. * Returns all values for the record.
  318. *
  319. * @return array
  320. */
  321. public function getValues() {
  322. return $this->values;
  323. }
  324. /**
  325. * Sets the value for a specific column.
  326. *
  327. * @param string $column_name
  328. * The name of the column to which the value should be set.
  329. * @param $value
  330. * The value to set.
  331. */
  332. public function setValue($column_name, $value) {
  333. // Make sure the column is valid.
  334. if (!in_array($column_name, $this->column_names)) {
  335. $message = t('ChadoRecord::setValue(). The column named, "!column", does not exist in table: "!table".',
  336. ['!column' => $column, '!table' => $this->table_name]);
  337. throw new Exception($message);
  338. }
  339. // Make sure that the value is not NULL if this is a required field.
  340. if (!in_array($column_name, $this->required_cols) and $value == '__NULL__') {
  341. $message = t('ChadoRecord::setValue(). The column named, "!column", requires a value for the table: "!table".',
  342. ['!column' => $rcol, '!table' => $this->table_name]);
  343. throw new Exception($message);
  344. }
  345. $this->values[$column_name] = $value;
  346. }
  347. /**
  348. * Returns the value of a specific column.
  349. *
  350. * @param string $column_name
  351. * The name of a column from the table from which to retrieve the value.
  352. */
  353. public function getValue($column_name) {
  354. // Make sure the column is valid.
  355. if (!in_array($column_name, $this->column_names)) {
  356. $message = t('ChadoRecord::getValue(). The column named, "!column", does not exist in table: "!table".',
  357. ['!column' => $column, '!table' => $this->table_name]);
  358. throw new Exception($message);
  359. }
  360. return $this->values[$column_name];
  361. }
  362. /**
  363. * Uses the current values given to this object to find a record.
  364. *
  365. * Use the setValues function first to set values for searching, then call
  366. * this function to find matching record. The values provided to the
  367. * setValues function must uniquely identify a record.
  368. *
  369. * @return
  370. * The number of matches found. If 1 is returned then the query
  371. * successfully found a match. If 0 then no matching records were found.
  372. *
  373. * @throws Exception
  374. */
  375. public function find() {
  376. // Make sure we have values for this record before searching.
  377. if (empty($this->values)) {
  378. $message = t('ChadoRecord::find(). Could not find a record from the table, !table, without any values.',
  379. ['!table' => $this->table_name]);
  380. throw new Exception($message);
  381. }
  382. // Build the SQL statement for searching.
  383. $select_args = [];
  384. $sql = 'SELECT * FROM {' . $this->table_name . '} WHERE 1=1 ';
  385. foreach ($this->values as $column => $value) {
  386. $sql .= ' AND ' . $column . ' = :' . $column;
  387. $select_args[':' . $column] = $value;
  388. }
  389. try {
  390. $results = chado_query($sql, $select_args);
  391. }
  392. catch (Exception $e) {
  393. $message = t('ChadoRecord::find(). Could not find a record in the table, !table, with the following values: !values. ERROR: !error',
  394. ['!table' => $this->table_name, '!values' => print_r($this->values, TRUE), '!error' => $e->getMessage()]);
  395. throw new Exception($message);
  396. }
  397. // If we only have a single match then we're good and we can update the
  398. // values for this object.
  399. $num_matches = $results->rowCount();
  400. if ($num_matches == 1) {
  401. $record = $results->fetchAssoc();
  402. $this->values = [];
  403. foreach ($record as $column => $value) {
  404. $this->values[$column] = $value;
  405. }
  406. $this->record_id = $record[$this->pkey];
  407. }
  408. // Return the number of matches.
  409. return $num_matches;
  410. }
  411. }