Documentation is available at StoredObject.inc
- <?php
- /**
- * StoredObject Class.
- *
- * A base class that contains tools for storing
- * and retrieving objects from a database
- * This is effectively an abstract class and
- * shuld never be instantiated directly.
- *
- * This file is part of CompInaBox.
- * @copyright Copyright 2001-2005. Eric D. Nielsen, All rights reserverd.
- * @license http://opensource.org/licenses/gpl-license.php GNU Public License
- * @author Eric D. Nielsen <nielsene@alum.mit.edu>
- *
- * @package ObjectRelationalMappingLayer
- */
- /**
- * StoredObject
- *
- * A base class that contains tools for storing
- * and retrieving objects from a database
- * @package ObjectRelationalMappingLayer
- * @access public
- * @abstract
- */
- class StoredObject
- {
- /**
- * Acts as DB primary key
- * @var string
- * @access private
- */
- var $primeField;
- /**#@+
- * @access private
- */
- /** Storeable member variables
- * @var array
- */
- var $fields;
- /** List of tables in the DB that the subtype uses
- * @var array
- */
- var $tables;
- /** What fields are out of date with the DB
- * @var array
- */
- var $dirty;
- /** Mapping member variabbles to column(s) in the DB's table(s)
- * @var array
- */
- var $fieldsToTables;
- /** Inverse mapping of {@link StoredObject::$fieldsToTables}
- * @var array
- */
- var $tablesToFields;
- /** Order in which tables should be processed
- * @var array
- */
- var $tableOrder;
- /**#@-*/
- /**
- * Database Abstraction Connection
- * @access private
- * @var DB
- */
- var $db;
- /**
- * StoredObject Constructor
- * @param DB $db the databasse to interact with
- */
- function StoredObject($db=0)
- {
- # note this is a abstract class, $primekey must be set
- $this->db=$db;
- $this->fields=array();
- $this->tables=array();
- $this->dirty=array();
- $this->fieldsToTables=array();
- $this->tablesToFields=array();
- $this->tableOrder=array();
- }
- /**
- * Generate a select statement for a given table.
- * Generates a select statement, selecting all registered
- * columns from a given single table, by the tables's primary
- * key columnm.
- * @access private
- * @param string $tableName the table a SELECT is needed for
- * @param array $table information about the table
- * @param boolean $multi do we need to subindex the member variables
- * @param int $m the subindex to use
- * @return string a full SELECT statement
- * @todo why passing $table not using $this->tables[$tableName]?
- * @todo why not default $multi and $m
- */
- function getSelectByIDString($tableName, $table,$multi,$m)
- {
- $result = "SELECT " . arrayToCSL($table[1]) . " FROM $tableName ";
- $tableKey = $table[0];
- $result .= "WHERE $tableKey = '";
- # echo printArray($this->tablesToFields);
- # echo printArray($this->fields);
- $fieldName = $this->tablesToFields[$tableKey][$tableName];
- if ($multi==1)
- $result .= $this->fields[$fieldName] . "';";
- else
- $result .= $this->fields[$fieldName][$m] . "';";
- return $result;
- }
- /**
- * Remove this stored object from the database.
- * Completely remove this stored object from the database. This
- * method relies on the the ON DELETE behavoir of the referenced
- * tabes to work. Only the row referenced by the primeField is deleted,
- * directly.
- * @access public
- * @todo add error reporting when ON DELETE RESTRICT?
- */
- function remove()
- {
- $tableName = $this->tableOrder[0];
- $tableKey = $this->tables[$tableName][0];
- $prime = $this->fields[$this->primeField];
- $query = "DELETE FROM $tableName WHERE $tableKey=$prime;";
- $this->db->query($query);
- }
- /**
- * Load the identified records into memory.
- * Fetch the referenced object from the database. If an $id is
- * passed in use that, if not, attempt to use $this->getID, if
- * neither fail.
- * @access public
- * @param mixed $id the primary key of the object to load
- * @return int 1 on success, 0 on fail
- */
- function retrieve($id=0)
- {
- $db=$this->db;
- if ($id!=0)
- $this->setID($id);
- $currentID = $this->getID();
- if (!$currentID)
- return 0; #no ID set
- $numTables = count($this->tables);
- for ($i=0;$i<$numTables;$i++)
- {
- $tableName = $this->tableOrder[$i];
- $table = $this->tables[$tableName];
- $tableKey = $this->tablesToFields[$table[0]][$tableName];
- $tableKeyValue = $this->fields[$tableKey];
- if (!is_array($tableKeyValue))
- $tableKeyValue = array($tableKeyValue);
- $numValues = count($tableKeyValue);
- for ($m=0;$m<$numValues;$m++)
- {
- if (!isset($this->fields[$tableKey]) || $tableKeyValue[$m]=="") continue;
- $query = $this->getSelectByIDString($tableName,$table,$numValues,$m);
- # echo $query . "<br>";
- $result = $db->query($query);
- $numFields = count($table[1]);
- switch($numRows = $result->numRows())
- {
- case 0 : break;
- case 1 :
- $temp = $result->getRowAt(0);
- for ($k=0;$k<$numFields;$k++)
- {
- $level1 = $table[1][$k];
- $field = $this->tablesToFields[$level1][$tableName];
- if ($numValues==1)
- $this->fields[$field] = $temp[$k];
- else
- $this->fields[$field][$m] = $temp[$k];
- }
- break;
- default:
- for ($j=0;$j<$numRows;$j++)
- {
- $temp = $result->getRowAt($j);
- for ($k=0;$k<$numFields;$k++)
- {
- $level1= $table[1][$k];
- $field = $this->tablesToFields[$level1][$tableName];
- if ($numValues==1)
- $this->fields[$field][$j] = $temp[$k];
- else
- $this->fields[$field][$m][$j] = $temp[$k];
- }
- }
- }
- }
- }
- return 1;
- }
- /**
- * Save/Update the object to the Database.
- * Either INSERTS or UPDATES, as appropriate, into the
- * database to bring the db upto date with the state of the
- * object. Does NOT do any checking for stale data.
- *
- * Clears the status of all dirty bits to reflect clean status.
- * @access public
- * @return mixed the primary key of the object
- */
- function postToDB()
- {
- if ($this->fields[$this->primeField]=="")
- $result = $this->insertNew();
- else
- $result = $this->update($this->fields[$this->primeField]);
- reset($this->fields);
- while ($aField = each($this->fields))
- {
- $temp=$aField['key'];
- $this->dirty[$temp]=FALSE;
- }
- return $this->fields[$this->primeField];
- }
- /**
- * INSERTS the object to the Database.
- * INSERTS into the
- * database to bring the db upto date with the state of the
- * object. Does NOT do any checking for stale data.
- *
- * @access private
- * @return mixed the primary key of the object
- */
- function insertNew()
- {
- reset($this->fields);
- $tablesInvolved = array();
- while ($field = each($this->fields))
- {
- if ($field['value']=="" ) continue;
- $tablesAffectedByField = $this->fieldsToTables[$field['key']];
- reset($tablesAffectedByField);
- while ($aTable = each($tablesAffectedByField))
- {
- if (in_array($aTable['key'],array_keys($tablesInvolved)))
- array_push($tablesInvolved[$aTable['key']],$this->fieldsToTables[$field['key']][$aTable['key']]);
- else
- $tablesInvolved[$aTable['key']]=array($this->fieldsToTables[$field['key']][$aTable['key']]);
- }
- }
- reset($tablesInvolved);
- while ($aTable = each($tablesInvolved))
- {
- $primeColumn = $this->tables[$aTable['key']][0];
- $tableName = $aTable['key'];
- $columns = $aTable['value'];
- $tablePrimeField = $this->tablesToFields[$primeColumn][$tableName];
- $arrayDepth=1;
- $numColumns = count($columns);
- for ($i=0;$i<$numColumns;$i++)
- {
- $tempFieldName = $this->tablesToFields[$columns[$i]][$tableName];
- $aColumn = $this->fields[$tempFieldName];
- if (is_array($aColumn) && (count($aColumn) > $arrayDepth))
- $arrayDepth = count($aColumn);
- }
- for ($i=0;$i<$arrayDepth;$i++)
- {
- $query = "INSERT INTO $tableName (";
- $queryPart2 = "values (";
- $first = TRUE;
- $tablePrimeValue = $this->fields[$tablePrimeField];
- if ($tablePrimeValue!="")
- {
- $needPrime=FALSE;
- if (!in_array($primeColumn,$columns))
- {
- $query .= "$primeColumn";
- $keyValue = $this->fields[$this->tablesToFields[$primeColumn][$tableName]];
- $queryPart2 .= "'$keyValue'";
- $first=FALSE;
- }
- }
- else
- {
- $needPrime=TRUE;
- # $primeSelect = "SELECT $primeColumn from $tableName WHERE ";
- }
- reset($columns);
- while ($aColumn = each($columns))
- {
- $columnName = $aColumn['value'];
- $fieldValue = $this->fields[$this->tablesToFields[$columnName][$tableName]];
- if (!$first)
- {
- $queryPart2 .= ", ";
- $query .= ", ";
- # if ($needPrime) $primeSelect .= "AND ";
- }
- else
- $first=FALSE;
- $query .= "$columnName";
- if (is_array($fieldValue))
- $queryPart2 .= "'$fieldValue[$i]'";
- else
- $queryPart2 .= "'$fieldValue'";
- # if ($needPrime)
- # {
- # if (is_array($fieldValue))
- # $primeSelect .= "$columnNames='fieldValue[$i]'";
- # else
- # $primeSelect .= "$columnName='$fieldValue'";
- # }
- }
- $query .= ") $queryPart2);";
- # echo "<br>".$query;
- $result = $this->db->query($query);
- if ($needPrime)
- {
- $lastOID = $this->db->getLastOID();
- $query = "SELECT $primeColumn FROM $tableName WHERE oid=$lastOID;";
- $result = $this->db->query($query);
- # $primeSelect .= ";";
- # $result = $this->db->query($primeSelect);
- $temp = $result->getRowAt(0);
- $keyField = $this->tablesToFields[$primeColumn][$tableName];
- $this->fields[$keyField] = $temp[0];
- }
- }
- }
- return $this->fields[$this->primeField];
- }
- /**
- * UPDATES the object to the Database.
- * UPDATES into the
- * database to bring the db upto date with the state of the
- * object. (This can include some INSERTS/DELETES when dealingn
- * with multi-valued variables -- aka lists.
- *
- * @access private
- * @return mixed the primary key of the object
- */
- function update($id)
- {
- $dirtyTables = array();
- reset($this->dirty);
- while ($aField = each($this->dirty))
- {
- if ($aField['value'])
- {
- $affectedTables = $this->fieldsToTables[$aField['key']];
- reset($affectedTables);
- while ($aTable = each($affectedTables))
- {
- if (in_array($aTable['key'],array_keys($dirtyTables)))
- array_push($dirtyTables[$aTable['key']],$this->fieldsToTables[$aField['key']][$aTable['key']]);
- else
- $dirtyTables[$aTable['key']]=array($this->fieldsToTables[$aField['key']][$aTable['key']]);
- }
- }
- }
- reset($dirtyTables);
- while ($aTable = each($dirtyTables))
- {
- $tableName = $aTable['key'];
- $primeColumn = $this->tables[$tableName][0];
- $columns = $aTable['value'];
- $tablePrimeField = $this->tablesToFields[$primeColumn][$tableName];
- $arrayDepth=0;
- $fieldIsArray=array();
- $numColumns= count($columns);
- for ($i=0;$i<$numColumns;$i++)
- {
- $tempFieldName = $this->tablesToFields[$columns[$i]][$tableName];
- $fieldIsArray[$tempFieldName]=FALSE;
- $aColumn = $this->fields[$tempFieldName];
- if (is_array($aColumn))
- {
- $fieldIsArray[$tempFieldName]=TRUE;
- if (count($aColumn) > $arrayDepth)
- $arrayDepth = count($aColumn);
- }
- }
- if (!$arrayDepth) # normal update
- {
- $tableKey = $this->tables[$tableName][0];
- $query = "UPDATE $tableName SET ";
- $dirtyFields = $aTable['value'];
- reset($dirtyFields);
- $first=TRUE;
- while ($aField = each($dirtyFields))
- {
- $columnName = $aField['value'];
- $fieldValue = $this->fields[$this->tablesToFields[$columnName][$tableName]];
- if (!$first) $query .= ", ";
- $query .= " $columnName ='$fieldValue' ";
- $first=FALSE;
- }
- $query .= "WHERE $tableKey = '$id';";
- # echo $query;
- $this->db->query($query);
- }
- else
- {
- #######################
- $objectType = get_class($this);
- $tempObject = new $objectType($this->db);
- $tempObject->setID($this->getID());
- $tempObject->retrieve();
- # echo printArray($tempObject);
- # find the array(s)
- $storedFields = $tempObject->fields;
- $storedIsArray = array();
- $storedDepth =1;
- reset($storedFields);
- while ($aField = each($storedFields))
- {
- if (is_array($aField['value']))
- {
- $storedIsArray[$aField['key']]=TRUE;
- if (($tempCount=count($aField['value']))>$storedDepth)
- $storedDepth=$tempCount;
- }
- }
- $inserts = array(); # for now we'll delete everything
- $deletes = array(); # and reinsert, later well also check
- # for updates to avoid delete/insert pairs
- for ($i=0;$i<$storedDepth;$i++)
- $deletes[$i][0]=$tempObject->fields[$tablePrimeField];
- reset($columns);
- $j=0;
- while ($aColumn = each($columns))
- {
- if ($primeColumn==$aColumn['value']) continue;
- $j++;
- $columnName = $aColumn['value'];
- $fieldValue = $tempObject->fields[$this->tablesToFields[$columnName][$tableName]];
- if (is_array($fieldValue))
- for ($i=0;$i<$storedDepth;$i++)
- $deletes[$i][$j] = $fieldValue[$i];
- else
- for ($i=0;$i<$storedDepth;$i++)
- $deletes[$i][$j]=$fieldValue;
- }
- for ($i=0;$i<$arrayDepth;$i++)
- $inserts[$i][0]=$this->fields[$tablePrimeField];
- reset($columns);
- $j=0;
- while ($aColumn = each($columns))
- {
- if ($primeColumn==$aColumn['value']) continue;
- $j++;
- $columnName = $aColumn['value'];
- $fieldValue = $this->fields[$this->tablesToFields[$columnName][$tableName]];
- if (is_array($fieldValue))
- for ($i=0;$i<$arrayDepth;$i++)
- $inserts[$i][$j] = $fieldValue[$i];
- else
- for ($i=0;$i<$arrayDepth;$i++)
- $inserts[$i][$j]=$fieldValue;
- }
- for ($i=0;$i<$storedDepth;$i++)
- {
- $query = "DELETE FROM $tableName WHERE $primeColumn ='";
- $query .= $deletes[$i][0];
- reset($columns);
- $j=0;
- while ($aColumn=each($columns))
- {
- if ($primeColumn==$aColumn['value']) continue;
- $j++;
- $query .= "' AND ";
- $query .= $aColumn['value'] . " = '";
- $query .= $deletes[$i][$j];
- }
- $query .= "';";
- $result = $this->db->query($query);
- }
- for ($i=0;$i<$arrayDepth;$i++)
- {
- $query = "INSERT INTO $tableName ($primeColumn";
- $query2 = "('".$inserts[$i][0]."'";
- reset($columns);
- $j=0;
- while ($aColumn=each($columns))
- {
- if ($primeColumn==$aColumn['value']) continue;
- $j++;
- $query .= ", " . $aColumn['value'];
- $query2 .= ", '" . $inserts[$i][$j] ."'";
- }
- $query .= ") values $query2 );";
- $result = $this->db->query($query);
- }
- }
- }
- return 1;
- }
- /**
- * Search on non-key data.
- * Searches for matching record(s) using whatever fields
- * have been set.
- *
- * @access public
- * @return mixed 0 on no match, list of primary keys if found
- */
- function searchDB()
- {
- $matches = array();
- reset($this->fields);
- $tablesInvolved = array();
- while ($field = each($this->fields))
- {
- if ($field['value']=="" ) continue;
- $tablesAffectedByField = $this->fieldsToTables[$field['key']];
- reset($tablesAffectedByField);
- while ($aTable = each($tablesAffectedByField))
- {
- if (in_array($aTable['key'],array_keys($tablesInvolved)))
- array_push($tablesInvolved[$aTable['key']],$this->fieldsToTables[$field['key']][$aTable['key']]);
- else
- $tablesInvolved[$aTable['key']]=array($this->fieldsToTables[$field['key']][$aTable['key']]);
- }
- }
- reset($tablesInvolved);
- $intermediateKeys=array();
- while ($aTable = each($tablesInvolved))
- {
- $primeColumn = $this->tables[$aTable['key']][0];
- $tableName = $aTable['key'];
- $columns = $aTable['value'];
- $query = "SELECT $primeColumn FROM $tableName WHERE ";
- reset($columns);
- $first=TRUE;
- while ($aColumn = each($columns))
- {
- $columnName = $aColumn['value'];
- $fieldValue = $this->fields[$this->tablesToFields[$columnName][$tableName]];
- if ($fieldValue == "")
- $fieldValue = $intermediateKeys[$columnName];
- if (!$first)
- $query .= "AND ";
- else
- $first=FALSE;
- $query .= "$columnName = '$fieldValue'";
- }
- $query .= ";";
- $result = $this->db->query($query);
- $numResults = $result->numRows();
- if ($numResults ==0) continue;
- $intermediateMatches = array();
- for ($i=0;$i<$numResults;$i++)
- {
- $aRow = $result->getRowAt($i);
- array_push($intermediateMatches, $aRow[0]);
- }
- if ($this->tablesToFields[$primeColumn][$tableName]==
- $this->primeField)
- {
- if (count($matches))
- $matches = array_intersect($matches,$intermediateMatches);
- else
- $matches= $intermediateMatches;
- }
- else
- $intermediateKeys[$primeColumn] = $intermediateMatches[0];
- }
- return $matches;
- }
- /**
- * Generic Getter.
- * Returns the value of the requested member variable.
- *
- * @access public
- * @param string $key member variable name
- * @return mixed the value of $key
- * @todo add check for $key in fields.
- */
- function getGeneralData($key)
- {
- return $this->fields["$key"];
- }
- /**
- * Get Primary Key.
- * Returns the value of the object's database primary key
- *
- * @access public
- * @return mixed the primary key value, or null if the item hasn't been saved
- */
- function getID()
- {
- return $this->fields[$this->primeField];
- }
- /**
- * Generic Setter.
- * Updates the referenced member variable to the new value and
- * sets the dirty bit for the member variable.
- *
- * @access public
- * @param string $key member variable name
- * @param mixed $value the value to set member variable $key to
- * @return int 1 on success, 0 otherwise
- */
- function setGeneralData($key, $value)
- {
- if (!in_array($key, array_keys($this->fields)))
- return 0;
- if ($this->fields[$key]!=$value)
- {
- $this->fields[$key]=$value;
- $this->dirty[$key]=TRUE;
- }
- return 1;
- }
- /**
- * Prime ID Setter.
- * Normally only used to set the ID before called retrieve,
- * support is questionable, probably broken for changing an
- * items primary key via this.
- *
- * @access public
- * @param mixed $id the prime key to use
- * @return int 1 on success
- */
- function setID($id)
- {
- $this->fields[$this->primeField] = $id;
- $this->dirty[$this->primeField] = TRUE;
- return 1;
- }
- /**
- * Register a table with the Object Relational Mapper.
- * Normally used in the constructor of a subtype to configure the
- * table->fields mapping.
- *
- * @access protected
- * @param string $tableName the name of the table to register
- * @param string $keyName the primary key column in the database
- * @param string $keyField the name of the corresponding field
- * @param array $columnNames a list of other columns to include
- * @param array $fieldNames a list of fields, index matched against $columnNames
- * @return int 1 on success, 0 on failure
- */
- function addTable($tableName,
- $keyName,$keyField,
- $columnNames, $fieldNames)
- {
- $numColumns = count($columnNames);
- $numFields = count($fieldNames);
- if ($numColumns != $numFields)
- return 0;
- if (!in_array($keyField,$this->fields))
- {
- $this->fields[$keyField]="";
- $this->dirty[$keyField]="";
- }
- if (!in_array($tableName,array_keys($this->tables)))
- {
- $this->tables[$tableName]=array($keyName,$columnNames);
- $this->tablesToFields[$keyName][$tableName]=$keyField;
- $this->fieldsToTables[$keyField][$tableName]=$keyName;
- $this->tableOrder[count($this->tableOrder)]=$tableName;
- }
- else
- {
- $cols = $this->tables[$tableName][1];
- $cols = array_unique(array_merge($cols,$columnNames));
- $this->tables[$tableName][1]=$cols;
- }
- for ($i=0;$i<$numColumns;$i++)
- {
- if (!in_array($fieldNames[$i],$this->fields))
- {
- $this->fields[$fieldNames[$i]]="";
- $this->dirty[$fieldNames[$i]]="";
- }
- $this->tablesToFields[$columnNames[$i]][$tableName]=$fieldNames[$i];
- $this->fieldsToTables[$fieldNames[$i]][$tableName]=$columnNames[$i];
- }
- return 1;
- }
- /**
- * De-Register a table with the Object Relational Mapper.
- * Provided for symmetry -- never used I beleive....
- *
- * @access protected
- * @param string $tableName the name of the table to de-register
- */
- function removeTable($table)
- {
- list($keyName, $columns) = $this->tables[$table];
- reset($columns);
- while($aColumn = each($columns))
- {
- $fieldName = $this->tablesToFields[$aColumn['value']][$table];
- unset($this->tablesToFields[$aColumn['value']][$table]);
- unset($this->fieldsToTables[$fieldName][$table]);
- if (!count($this->tablesToFields[$aColumn['value']]))
- unset($this->tablesToFields[$aColumn['value']]);
- if (!count($this->fieldsToTables[$fieldName]))
- {
- unset($this->fields[$fieldName]);
- unset($this->dirty[$fieldName]);
- unset($this->fieldsToTables[$fieldName]);
- }
- }
- unset($this->tables[$table]);
- $start = $this->tableOrder[$table];
- $end = count($this->tableOrder);
- for ($i=$start;$i<$end-1;$i++)
- $this->tableOrder[$i]=$this->tableOrder[$i+1];
- unset($this->tableOrder[$i]);
- }
- }
- ?>
Documentation generated on Tue, 25 Apr 2006 13:11:11 -0400 by phpDocumentor 1.3.0RC3