tripal_chado/src/ChadoCustomTables/ChadoCustomTable.php
<?php
namespace Drupal\tripal_chado\ChadoCustomTables;
class ChadoCustomTable {
/**
* The name of the table.
*
* @var string
*/
private $table_name;
/**
* The ID of the table.
*
* @var int
*/
private $table_id;
/**
* The name of the Chado schema to use.
*
* @var string
*/
private $chado_schema;
/**
* Initializes the service object with a table name.
*
* This object will work with the custom table in the default Chado schema.
* Be sure to call the setSchemaName() on the ChadoConnection object to
* ensure the custom table is managed in the correct Chado instance.
*
* @param string $table_name
* The name of the custom table.
* @param string $chado_schema
* Optional. The chado schema where the custom table will live. If no
* schema is specified then the default schema is used.
*/
public function __construct($table_name, string $chado_schema = NULL) {
$this->table_name = NULL;
$this->table_id = NULL;
$this->chado_schema = NULL;
if (!$table_name) {
throw new \Exception('Please provide a value for the $table_name argument');
}
$this->table_name = $table_name;
$this->chado_schema = $chado_schema;
if (!$chado_schema) {
$chado = \Drupal::service('tripal_chado.database');
$this->chado_schema = $chado->getSchemaName();
}
$this->setTableId();
// If this table doesn't exist (i.e. it has no ID) then create
// an empty record for it.
if (!$this->table_id) {
$public = \Drupal::database();
$insert = $public->insert('tripal_custom_tables');
$insert->fields([
'table_name' => $this->table_name,
'schema' => '',
'chado' => $this->chado_schema,
]);
$table_id = $insert->execute();
if (!$table_id) {
throw New \Exception('Could not add the custom table, "' . $this->table_name .
'" for the Chado schema "' . $this->chado_schema .'".');
}
$this->setTableId();
}
}
/**
* Returns a ChadoConnection object with the correct schema set.
*
* This is just a helpder function for this class to make sure the
* Chado schema is set as requested anytime the object is needec.
*
* @return \Drupal\tripal_chado\Database\ChadoConnection
*/
protected function getChado() {
$chado = \Drupal::service('tripal_chado.database');
if ($this->chado_schema) {
$chado->setSchemaName($this->chado_schema);
}
return $chado;
}
/**
* Sets the private table_id member variable.
*/
private function setTableId() {
$public = \Drupal::database();
$query = $public->select('tripal_custom_tables','ct');
$query->fields('ct', ['table_id']);
$query->condition('ct.table_name', $this->table_name);
$query->condition('ct.chado', $this->chado_schema);
$results = $query->execute();
$this->table_id = $results->fetchField();
}
/**
* Retrieves the numeric ID of the custom table.
*
* @return integer
*/
public function getTableId() {
return $this->table_id;
}
/**
* Retrieves the name of the custom table.
*
* @return string
*/
public function getTableName() {
return $this->table_name;
}
/**
* Retrieves the name of the Chado schema in which this table lives.
*
* @return string
*/
public function getChadoSchema() {
return $this->chado_schema;
}
/**
* Toggles the custom table's hidden stuats.
*
* Tables that are hidden are meant to be managed internally by the
* Tripal module that created it and should not be changed or deleted by
* the end-user.
*
* @param bool $hide
* Set to True to hide the table. Set to False to show the table to the
* end-user.
*/
public function setHidden($hide = False) {
$public = \Drupal::database();
$update = $public->update('tripal_custom_tables');
$update->fields(['hidden' => $hide == TRUE ? 1 : 0]);
$update->condition('table_name', $this->table_name);
$update->condition('chado', $this->chado_schema);
$update->execute();
}
/**
* Indicates if the custom table is hidden from the end-user.
*
* Tables that are hidden are meant to be managed internally by the
* Tripal module that created it and should not be changed or deleted by
* the end-user.
*/
public function isHidden() {
$public = \Drupal::database();
$query = $public->select('tripal_custom_tables','tct');
$query->fields('tct', ['hidden']);
$query->condition('ct.table_name', $this->table_name);
$query->condition('ct.chado', $this->chado_schema);
$hidden = $query->execute()->fetchField();
if ($hidden == 1) {
return True;
}
return False;
}
/**
* Retrieves the schema for the custom table.
*
* If return value is empty then it means the table schema has not yet
* been provided or the init() function has not been called. Use
* the setTableSchema() function to provide one.
*
* @return array
*/
public function getTableSchema() {
$logger = \Drupal::service('tripal.logger');
if (!$this->table_id) {
$logger->error('Cannot get the the custom table schema. Please, first run the init() function.');
return [];
}
$public = \Drupal::database();
$query = $public->select('tripal_custom_tables','tct');
$query->fields('tct', ['schema']);
$query->condition('tct.table_name', $this->table_name);
$query->condition('tct.chado', $this->chado_schema);
$table_schema = $query->execute()->fetchField();
if (!$table_schema) {
return [];
}
return unserialize($table_schema);
}
/**
* Sets the table schema.
*
* When setting the table schema, the table will be created in the Chado
* schema if it doesn't exist. If the table does exist then the $force
* argument must be set to True and the table will be dropped and recreated.
* If not set to True then no change is made to the schema or the custom
* table. The force argument is to prevent accidental deletion and recreation
* of tables that may have data.
*
* If a mistake was made in the schema definition and it needs correction
* make sure the $force argument is set to False. But be careful. If the
* schema does not properly match the table problems may occur when using
* the table later.
*
* @param array $schema
* The Drupal table schema array defining the table.
* @param boolean $force
* True if the custom table should be dropped and recreated if it already
* exists
* @return boolean
* True on successfu
*/
public function setTableSchema($table_schema, $force = False) {
$logger = \Drupal::service('tripal.logger');
if (!$this->table_id) {
$logger->error('Cannot set the schema for the custom table. Please, first run the init() function.');
return False;
}
$public = \Drupal::database();
$chado = $this->getChado();
$transaction_chado = $chado->startTransaction();
try {
// Don't set the schema if it's not valid.
$errors = ChadoCustomTable::validateTableSchema($table_schema);
if (!empty($errors)) {
return False;
}
// If the table name is the same and the user isn't forcing any changes then
// create the table if it doesn't exist. If it does exist then leave the
// table as is and the function will later update the saved schema.
if ($force == False and $this->table_name == $table_schema['table']) {
$table_exists = $chado->schema()->tableExists($this->table_name);
if (!$table_exists) {
$chado->schema()->createTable($this->table_name, $table_schema);
}
}
// If the table name is the same and the user is forcing a change then
// create the table if it doesn't exist. If it does exist then drop it
// and recreate it.
if ($force == True and $this->table_name == $table_schema['table']) {
if ($chado->schema()->tableExists($this->table_name)) {
$chado->schema()->dropTable($this->table_name);
}
$chado->schema()->createTable($this->table_name, $table_schema);
}
// If the table name is different in the provided schema but the user is not
// forcing a change then this shouldn't be allowed. We don't want to update
// the saved schema with a table name mistmatch.
if ($force == False and $this->table_name != $table_schema['table']) {
$logger->error('Cannot change the name of the table in the schema without forcing it..');
return False;
}
// If the table name is different and the force argument is true, then the
// user is requesting a rename of the table. Make sure the name isn't
// already taken. If not, then drop the old table and create the new one.
if ($force == True and $this->table_name != $table_schema['table']) {
// First check if the new table exists and if so return False.
if ($chado->schema()->tableExists($table_schema['table'])) {
$logger->error('Cannot rename the table as another table exists with the same name.');
return False;
}
// Second, if the original table exists then delete it.
if ($chado->schema()->tableExists($this->table_name)) {
$chado->schema()->dropTable($this->table_name);
}
$chado->schema()->createTable($table_schema['table'], $table_schema);
}
$update = $public->update('tripal_custom_tables');
$update->fields([
'table_name' => $table_schema['table'],
'schema' => serialize($table_schema)
]);
$update->condition('table_id', $this->table_id);
$update->condition('chado', $chado->getSchemaName());
$update->execute();
}
catch (Exception $e) {
$transaction_chado->rollback();
$logger->error($e->getMessage());
return False;
}
return True;
}
/**
* Ensures that the table schema is correctly formatted.
*
* Returns a list of messages indicating if any errors are present.
*
* @param string $table_schema
* The Drupal table schema array defining the table.
*
* @return array
* A list of error message strings indicating what is wrong with the
* schema. If the array is empty then no errors were detected.
*/
static public function validateTableSchema($table_schema) {
$messages = [];
$logger = \Drupal::service('tripal.logger');
if (!$table_schema) {
$message = 'The custom table schema is empty.';
$messages[] = $message;
$logger->error($message);
return $messages;
}
if ($table_schema and !is_array($table_schema)) {
$message = 'The custom table schema is not an array';
$messages[] = $message;
$logger->error($message);
return $messages;
}
if (is_array($table_schema) and !array_key_exists('table', $table_schema)) {
$message = "The schema array must have key named 'table'";
$messages[] = $message;
$logger->error($message);
}
if (preg_match('/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/', $table_schema['table'])) {
$message = "Postgres will automatically change the table name to lower-case. To prevent unwanted side-effects, please rename the table with all lower-case characters.";
$messages[] = $message;
$logger->error($message);
}
// Check index length.
if (array_key_exists('indexes', $table_schema)) {
foreach (array_keys($table_schema['indexes']) as $index_name) {
if (strlen($table_schema['table'] . '_' . $index_name) > 60) {
$message = "One or more index names appear to be too long. For example: '" .
$table_schema['table'] . '_' . $index_name . ".' Index names are created by " .
"concatenating the table name with the index name provided " .
"in the 'indexes' array of the schema. Please alter any indexes that " .
"when combined with the table name are longer than 60 characters.";
$messages[] = $message;
$logger->error($message);
}
}
}
return $messages;
}
/**
* Destroyes the custom table completely.
*
* Tripal will no longer know about the table and the table will be removed
* from Chado. After this function is executed this object is no longer
* usable.
*
* @return bool
* True if successful. False otherwise.
*/
public function destroy() {
$logger = \Drupal::service('tripal.logger');
if (!$this->table_id) {
$logger->error('Cannot destroy the custom table. Please, first run the init() function.');
return False;
}
$public = \Drupal::database();
$delete = $public->delete('tripal_custom_tables');
$delete->condition('table_id', $this->table_id);
$delete->execute();
$this->deleteCustomTable();
$this->table_id = NULL;
$this->table_name = NULL;
return True;
}
/**
* Deletes the table in Chado.
*
* @return bool
* True if successful. False otherwise.
*/
private function deleteCustomTable() {
$logger = \Drupal::service('tripal.logger');
$chado = $this->getChado();
$transaction_chado = $chado->startTransaction();
$table_exists = $chado->schema()->tableExists($this->table_name);
if (!$table_exists) {
return True;
}
try {
$chado->schema()->dropTable($this->table_name);
if ($chado->schema()->tableExists($this->table_name)) {
$logger->error('Could not delete the ' . $this->table_name . ' table. Check the database server logs.');
return False;
}
}
catch (Exception $e) {
$transaction_chado->rollback();
$logger->error($e->getMessage());
return False;
}
return True;
}
}