diff options
Diffstat (limited to 'classes/backends')
-rw-r--r-- | classes/backends/BiblioIsisDb.php | 418 | ||||
-rw-r--r-- | classes/backends/IsisDb.php | 63 | ||||
-rw-r--r-- | classes/backends/MaleteDb.php | 161 | ||||
-rw-r--r-- | classes/backends/PhpIsisDb.php | 158 | ||||
-rw-r--r-- | classes/backends/SchemaDb.php | 96 |
5 files changed, 896 insertions, 0 deletions
diff --git a/classes/backends/BiblioIsisDb.php b/classes/backends/BiblioIsisDb.php new file mode 100644 index 0000000..bb7f9f5 --- /dev/null +++ b/classes/backends/BiblioIsisDb.php @@ -0,0 +1,418 @@ +<?php + +/** + * Biblio::Isis implementation of IsisDb. + */ +class BiblioIsisDb implements IsisDb { + /** + * @var $fdt + * Field description table. + */ + var $fdt; + + /** + * Class instance of a perl interpreter; + */ + var $perl; + + /** + * @var $format + * Database format, derived from $schema. + */ + var $format; + + /** + * @var $log + * Class action log. + */ + var $log; + + /** + * Constructor. + * + * @see IsisDb::__construct() + */ + public function __construct($schema) { + // Save db schema. + $this->format = $schema; + + // Setup $fdt. + foreach ($schema['fields'] as $field => $info) { + $this->fdt[$field] = $info['name']; + } + + // Create a perl instance. + $this->perl = new Perl(); + } + + /** + * Class logger. + * + * @param $message + * Log message. + */ + function logger($message) { + $this->log[] = $message; + } + + /** + * Send requests to the perl backend. + * + * @param $method + * Backend method name to invoke. + * + * @param $args + * Backend method arguments. + * + * @return + * Backend return value. + */ + function backend($method = 'count', $args = NULL) { + // Setup the database. + $name = $this->format['db']['name']; + $db = Cinisis::file("$name/$name", 'db'); + + // Setup arguments. + if ($args != NULL) { + $args = '('. $args .')'; + } + + try { + // Call backend. + return $this->perl->eval(' + use Biblio::Isis; + + my $isis = new Biblio::Isis( + isisdb => "'. $db .'", + ); + + return $isis->'. $method . $args .';'); + } + catch (PerlException $exception) { + echo __CLASS__ .': Perl error: ' . $exception->getMessage(); + return FALSE; + } + } + + /** + * Read an entry. + * + * @param $id + * Record Id. + * + * @param $method + * Database read method. + * + * @see IsisDb::read() + */ + public function read($id, $method = 'fetch') { + // Database query. + $results = $this->backend($method, $id); + + if ($results) { + // Tag results. + $data = $this->tag($results, $method); + + // Charset conversion. + if (is_array($data)) { + array_walk_recursive($data, array(__CLASS__, 'charset')); + } + + // Return the result. + return $data; + } + } + + /** + * Return number of entries in the database. + * + * @see IsisDb::entries() + */ + public function entries() { + return $this->backend('count'); + } + + /** + * Return an example schema. + * + * @see IsisDb::example() + */ + public function example() { + return SchemaDb::example(); + } + + /** + * Check configuration. + * + * @see IsisDb::check() + */ + static function check($schema, $section = NULL) { + // Check API availability. + if (!class_exists('Perl')) { + throw new Exception('Could not find Perl class. Please check your php-perl installation.'); + return FALSE; + } + + // Check schema configuration. + return SchemaDb::check($schema, $section); + } + + /** + * Tag results of a db query. + * + * This function converts the keys of query result from field numbers + * to names. + * + * @param $results + * Database query results. + * + * @param $method + * Database read method. + * + * @return + * Tagged database result. + */ + function tag($results, $method = 'fetch') { + foreach ($results as $key => $value) { + // Key '000' used to hold MFN. + if ($key != '000') { + if (!isset($this->format['fields'][$key])) { + continue; + } + + // Format, repetition and subfield handling. + $name = $this->format['fields'][$key]['name']; + $data[$name] = $this->repetition($key, $value); + $data[$name] = $this->subfields($data[$name], $key, $method); + } + } + + return $data; + } + + /** + * Checks whether a field has subfields. + * + * @param $key + * Field key. + * + * @return + * True if has subfields, false otherwise. + */ + function has_subfields($key) { + if (isset($this->format['fields'][$key]['subfields'])) { + return TRUE; + } + + return FALSE; + } + + /** + * Switch keys on subfields. + * + * @param $key + * Field key. + * + * @param $value + * Dataset. + */ + function subfields_switch($key, &$value) { + if (!is_array($value)) { + return; + } + + foreach ($value as $subkey => $subvalue) { + if (isset($this->format['fields'][$key]['subfields'][$subkey])) { + $subname = $this->format['fields'][$key]['subfields'][$subkey]; + } else { + $subname = $subkey; + } + + $value[$subname] = $subvalue; + + if ($subkey != $subname) { + unset($value[$subkey]); + } + } + } + + /** + * Makes subfield substitution in a dataset. + * + * @param $name + * Dataset. + * + * @param $key + * Field key. + * + * @param $method + * Database read method. + * + * @return + * Data with processed subfields. + */ + function subfields($name, $key, $method) { + if ($this->has_subfields($key) && is_array($name)) { + $method = 'subfields_from_'. $method; + return $this->{$method}($name, $key); + } + else { + foreach ($name as $value) { + $data[] = array(Cinisis::main_field_name($this->format, $key) => $value); + } + } + + return $data; + } + + /** + * Subfield handling for data read by 'to_hash' method. This method + * is not fully supported and therefore not recommended. + * + * It does not deal very well when data has "main" fields and + * subfields (like "data1^adata2^bdata3") and doesn't deal with + * advanced configuration such as 'join_subfields'. + * + * @param $name + * Dataset. + * + * @param $key + * Field key. + * + * @return + * Data with processed subfields. + */ + function subfields_from_to_hash($name, $key) { + if ($this->is_repetitive($key, $name)) { + foreach ($name as $entry => $value) { + $this->subfields_switch($key, $value); + $name[$entry] = $value; + } + } + else { + $this->subfields_switch($key, $name); + } + + return $name; + } + + /** + * Subfield handling for data read by 'from_fetch' method. + * + * @param $name + * Dataset. + * + * @param $key + * Field key. + * + * @return + * Data with processed subfields. + */ + function subfields_from_fetch($name, $key) { + // Check if entry has repetitions. + $this->is_repetitive($key, $name); + + // Iterate over all values. + foreach ($name as $entry => $value) { + if (substr($value, 0, 1) != '^') { + $field = preg_replace('/\^.*/', '', $value); + $subfields = substr($value, strlen($field) + 1); + $subfields = (!empty($subfields)) ? $subfields = explode('^', $subfields) : array(); + + if (isset($field)) { + $data[$entry]['field'] = $field; + } + } + else { + $subfields = explode('^', substr($value, 1)); + } + + // Subfield tagging. + foreach ($subfields as $subfield => $subvalue) { + $subkey = substr($subvalue, 0, 1); + if (isset($this->format['fields'][$key]['subfields'][$subkey])) { + $subkey = $this->format['fields'][$key]['subfields'][$subkey]; + } + $data[$entry]['subfields'][$subkey] = substr($subvalue, 1); + } + + // Join subfields and main field if needed. + if (Cinisis::join_subfields($this->format)) { + if (isset($data[$entry]['subfields'])) { + $data[$entry] = $data[$entry]['subfields']; + } + + if (isset($field)) { + unset($data[$entry]['field']); + $data[$entry][Cinisis::main_field_name($this->format, $key)] = $field; + } + } + } + + return $data; + } + + /** + * Deals with repetition. + * + * As Biblio::Isis always return field values as arrays, we + * have to check the database schema to see if we want to + * convert then to a single value. + * + * @param $field + * Database field. + * + * @param $value + * Data (with or without repetition). + * + * @return + * True if repetitive, false otherwise. + */ + function is_repetitive($field, $value) { + if (isset($this->format['fields'][$field]['repeat']) && + $this->format['fields'][$field]['repeat'] == FALSE) { + if (is_array($value) && count($value) > 1) { + $this->logger("Field $field is configured as non repetitive but data shows a repetition for value."); + } + return FALSE; + } + + return TRUE; + } + + /** + * Deals with repetition. + * + * As Biblio::Isis always return field values as arrays, we + * have to check the database schema to see if we want to + * convert then to a single value. The current implementation + * is just a placeholder as no conversion is done. + * + * @param $key + * Database key. + * + * @param $value + * Query field result. + * + * @return + * The value according to the repetition config. + */ + function repetition($key, $value) { + return $value; + } + + /** + * Charset conversion. + * + * Converts a string from the database charset to UTF-8. + * + * @param $data + * String to be converted. + * + * @return + * String converted to UTF-8. + */ + function charset(&$data) { + $data = iconv($this->format['db']['charset'], 'UTF-8', $data); + } +} diff --git a/classes/backends/IsisDb.php b/classes/backends/IsisDb.php new file mode 100644 index 0000000..4a2218a --- /dev/null +++ b/classes/backends/IsisDb.php @@ -0,0 +1,63 @@ +<?php + +/** + * Generic interface for reading Isis databases. + */ +interface IsisDb { + /** + * Constructor. + * + * The implementation constructor should accept a database + * schema definition and setup the appropriate db resource. + * + * @param $schema + * High level database schema description. + * + * @return + * Database resource or FALSE in case of error. + * + * @see default_schema() + */ + public function __construct($schema); + + /** + * Read an entry from the database. + * + * @param $id + * Database entry id. + */ + public function read($id); + + /** + * Return number of entries in the database. + * + * @return + * Number of entries in the database. + */ + public function entries(); + + /** + * Return an example database schema. + * + * The example schema should have all information the implementation + * needs to be able to open and read a database. + * + * @return + * Array with a sample database schema. + */ + public function example(); + + /** + * Configuration check. + * + * @param $schema + * Database schema to check. + * + * @param $section + * Configuration section. + * + * @return + * Database schema or FALSE if error. + */ + static function check($schema, $section = NULL); +} diff --git a/classes/backends/MaleteDb.php b/classes/backends/MaleteDb.php new file mode 100644 index 0000000..114995b --- /dev/null +++ b/classes/backends/MaleteDb.php @@ -0,0 +1,161 @@ +<?php + +/** + * Malete implementation of IsisDb. + * + * @warning + * This implementation is currently outdated and lacks + * basic functionalities such as subfield handling and + * therefore it's use is not recommended. + */ +class MaleteDb implements IsisDb { + /** + * @var $fdt + * Field description table. + */ + var $fdt; + + /** + * @var $db + * Database resource. + */ + var $db; + + /** + * @var $format + * Database format, derived from $schema. + */ + var $format; + + /** + * @var $log + * Class action log. + */ + var $log; + + /** + * Constructor. + * + * @see IsisDb::__construct() + */ + public function __construct($schema) { + // Save db schema. + $this->format = $schema; + + // Setup $fdt used by malete. + foreach ($schema['fields'] as $field => $info) { + $this->fdt[$field] = $info['name']; + } + + // Open a database connection. + $this->db = new Isis_Db($this->fdt, $schema['db']['name'], new Isis_Server()); + if (!$this->db->srv->sock) { + return FALSE; + } + } + + /** + * Read an entry. + * + * @see IsisDb::read() + * + * @todo + * Subfield handling. + */ + public function read($id) { + if (!is_numeric($id)) { + return FALSE; + } + if ($results !== FALSE) { + $results = $this->db->read($id); + return $this->tag($results); + } + else { + return FALSE; + } + } + + /** + * Return number of entries in the database. + * + * The Malete API doen't implement such feature so we + * have to emulate it by iterating over all entries + * until MaleteDb::read() returns FALSE. + * + * @see IsisDb::entries() + */ + public function entries() { + // The first entry in a malete database has id 1 and + // not 0, therefore $id's initial value should be 1. + $id = 1; + while($this->db->read($id)) { + $id++; + } + return $id - 1; + } + + /** + * Return an example schema. + * + * @see IsisDb::example() + */ + public function example() { + return SchemaDb::example(); + } + + /** + * Check configuration. + * + * @see IsisDb::check() + */ + public function check($schema, $section = NULL) { + // Check API availability. + if (!class_exists('Isis_Db')) { + throw new Exception('Could not find Isis_Db class. Please check your malete installation.'); + return FALSE; + } + + // Check schema configuration. + return SchemaDb::check($schema, $section); + } + + /** + * Tag results of a db query. + * + * This function converts the keys of query result from field numbers + * to names and and also puts repetition fields into place as Malete + * deals with field repetition by using a 'tag' property in the resulting + * query object. + * + * @param $results + * Database query results. + * + * @return + * Tagged database result. + */ + function tag($results) { + foreach ($results->val as $key => $value) { + $field = $results->tag[$key]; + $name = $this->format['fields'][$field]['name']; + + // Handles field repetition. + if ($this->format['fields'][$field]['repeat']) { + $data[$name][] = $value; + } + else { + $data[$name] = $value; + } + } + return $data; + } + + /** + * Class logger. + * + * @param $message + * Log message. + */ + function logger($message) { + $this->log[] = $message; + } +} diff --git a/classes/backends/PhpIsisDb.php b/classes/backends/PhpIsisDb.php new file mode 100644 index 0000000..e69898e --- /dev/null +++ b/classes/backends/PhpIsisDb.php @@ -0,0 +1,158 @@ +<?php + +/** + * PHP-Isis implementation of IsisDb. + * + * @warning + * This implementation is currently outdated and lacks + * basic functionalities such as subfield handling and + * therefore it's use is not recommended. + */ +class PhpIsisDb implements IsisDb { + /** + * @var $db + * Database resource. + */ + var $db; + + /** + * @var $format + * Database format, derived from $schema. + */ + var $format; + + /** + * @var $log + * Class action log. + */ + var $log; + + /** + * Constructor. + * + * @see IsisDb::__construct() + */ + public function __construct($schema) { + // Save db schema. + $this->format = $schema; + + // Setup $fdt. + foreach ($schema['fields'] as $field => $info) { + $this->fdt[$field] = $info['name']; + } + + // Open the database. + $name = $schema['db']['name']; + $this->db = isis_open(Cinisis::file("$name/$name", 'db')); + } + + /** + * Read an entry. + * + * The PHP-Isis API doen't implement such feature so we + * have to emulate it by geting all entries and using + * isis_data_seek() to get the desired record. + * + * @see IsisDb::read() + * + * @todo + * Subfield handling. + */ + public function read($id) { + $results = isis_search('$', $this->db); + if (!isis_data_seek($results, $id - 1)) { + return FALSE; + } + + // Tag results. + $data = $this->tag(isis_fetch_array($results)); + + // Charset conversion. + array_walk_recursive($data, array(__CLASS__, 'charset')); + + // Return the result. + return $data; + } + + /** + * Return number of entries in the database. + * + * @see IsisDb::entries() + */ + public function entries() { + return isis_last_mfn($this->db); + } + + /** + * Return an example schema. + * + * @see IsisDb::example() + */ + public function example() { + return SchemaDb::example(); + } + + /** + * Check configuration. + * + * @see IsisDb::check() + */ + static function check($schema, $section = NULL) { + // Check API availability. + if (!function_exists('isis_open')) { + throw new Exception('Could not find function isis_open. Please check your php-isis installation.'); + return FALSE; + } + + // Check schema configuration. + return SchemaDb::check($schema, $section); + } + + /** + * Tag results of a db query. + * + * This function converts the keys of query result from field + * numbers to names. + * + * @param $results + * Database query results. + * + * @return + * Tagged database result. + */ + function tag($results) { + foreach ($results as $key => $value) { + if ($key != 'mfn') { + $name = $this->format['fields'][$key]['name']; + $data[$name] = $value; + } + } + + return $data; + } + + /** + * Charset conversion. + * + * Converts a string from the database charset to UTF-8. + * + * @param $data + * String to be converted. + * + * @return + * String converted to UTF-8. + */ + function charset(&$data) { + $data = iconv($this->format['db']['charset'], 'UTF-8', $data); + } + + /** + * Class logger. + * + * @param $message + * Log message. + */ + function logger($message) { + $this->log[] = $message; + } +} diff --git a/classes/backends/SchemaDb.php b/classes/backends/SchemaDb.php new file mode 100644 index 0000000..db9ca30 --- /dev/null +++ b/classes/backends/SchemaDb.php @@ -0,0 +1,96 @@ +<?php + +/** + * SchemaDb class with standard database procedures and + * configuration. + */ +class SchemaDb { + /** + * Return the required database config. + * + * @return + * Array with required config. + */ + static function required() { + $schema = array( + 'db' => array( + 'name' => 'dbname', + ), + 'fields' => array( + ), + ); + + return $schema; + } + + /** + * Return the optional database config. + * + * @return + * Array with optional config. + */ + public function optional() { + $schema = array( + 'db' => array( + 'charset' => 'charset', + ), + ); + + return $schema; + } + + /** + * Return an example database schema. + * + * @see IsisDb::example() + */ + public function example() { + $required = SchemaDb::required(); + $optional = SchemaDb::optional(); + $schema = array( + 'db' => array( + 'charset' => 'charset', + ), + 'fields' => array( + 1 => array( + 'name' => 'field_name', + 'size' => 1000, + 'format' => 'numeric', + 'repeat' => TRUE, + 'subfields' => array( + 'a' => 'test', + 'b' => 'test2', + ), + ), + ), + ); + + return array_merge_recursive($required, $optional, $schema); + } + + /** + * Recursively check for required fields in a database schema. + * + * @see IsisDb::check() + */ + static function check($schema, $section = NULL) { + if ($section === NULL) { + $section = SchemaDb::required(); + } + + foreach ($section as $key => $value) { + if (!isset($schema[$key])) { + throw new Exception('Undefined required parameter '. $key .' on database configuration.'); + return FALSE; + } + + if (is_array($value)) { + if (SchemaDb::check($schema[$key], $section[$key]) == FALSE) { + return FALSE; + } + } + } + + return $schema; + } +} |