Source for file ExternallyLinkedORM.php
Documentation is available at ExternallyLinkedORM.php
* Created on Sep 26, 2008
* To change the template for this generated file go to
* Window - Preferences - PHPeclipse - PHP - Code Templates
define('ALLDATA', 'allRecords');
* Class: ExternallyLinkedORM
* @remember currently the references mechanism only works well if the foreign key field refers to
* the primary key field of the referenced table; also, this DDMS works on the assumption that primary key
* fields are integer values. local primary key fields must be autoincrement and the field name must be 'local_key'
*@todo possibly, as a step towards handling insertion of new records: allow for new records to be inserted but to remain
* purely local, recognisable by null value in remote key field. These records will be ignored during the refresh event, ie
* they will not be updated or deleted at any stage.
* @todo possibly, include non-linked tables in the references table to indicate how these remotely linked cached data records
* relate to the local non-linked application data. Then upon deletion of records that are referenced by local tables the user
* should have a choice not to delete those records in order to avoid data corruption. These records can then also continue as purely local records even though stored in linked table.
* Alternatively, a separate table could be created for these purely local records.
* @todo the update function can now run more than once for the same table during
* a refresh data event (eg during its regular update, but also in the case when a record
* is deleted in a table that is has foreign key references to). To maximise efficiency
* we could consider keeping an array with key values that have already been updated during the current
* refresh event, and before running another update check against this array. Alternatively, make the update
* function only ever execute once during any given refresh event, ie build up an array with key values
* and only if it is complete run the update event.
* @todo rename the different refresh functions to 'update' 'insert' 'delete', separate out delete and insert
* at start of refresh event retrieve set of remote keys as these are used in all three
* @todo for new records: go back to the server to check for foreign key references of other tables and
* optionally refresh these in the local cache
* @todo separate out the ALLDATA refresh alltogether
* @param Associative array $tableDetails.
* Expected array keys: table_name, primary_key, remote_key
* @param Associative array $references, representing foreign key references of the linked table.
* Expected array keys: `column`, referenced_table, referenced_column, remote_referenced_column
* @param ExternalDataServerConnection $serverConnection.
* Reference to the serverConnection object
function __construct($tableDetails, $references, &$serverConnection)
$this->database = 'rigregversiontwo';
$this->table["$tableDetails[table_name]"] = $tableDetails['table_name'];
$this->tableName = $tableDetails['table_name'];
//setup the table with the help of the MySql information_schema
$link = mysql_connect('localhost', 'root', '') or die('Could not connect: ' .
$sql = "SELECT COLUMN_NAME,
FROM information_schema.COLUMNS C
where TABLE_SCHEMA = '$this->database'
$this->map["$row[0]"] = array("$this->tableName", "$row[0]");
$sql = "SELECT CHARACTER_MAXIMUM_LENGTH
FROM information_schema.COLUMNS C
where TABLE_SCHEMA = '$this->database'
$this->remoteKey = $tableDetails['remote_key'];
$sql = "SELECT CHARACTER_MAXIMUM_LENGTH
FROM information_schema.COLUMNS C
where TABLE_SCHEMA = '$this->database'
//make the map that will be used to communicate with the server
//methods to help constructing the Object
//-------------------------------methods to get information about this linked table-------------------------
* @return TRUE if the linked table primary key field is the remote_key field
* FALSE if the linked table primary key field is not the remote_key field
if (!$this->num_rows() == 0) {
* @return if the linked table has references to
* other linked tables: TRUE, else FALSE
* @method hasLocalKeyReferences
* @return if the linked table has references to
* local_key values of other linked tables: TRUE, else FALSE
if ($reference['referenced_column'] == 'local_key')
//-------------------------------end methods to get information about this linked table-------------------------
//---------methods to get information about specific references belonging to this table------------------
* @method isLocalKeyReference *
* @param array $reference
* @return if 'referenced_column' == 'local_key' TRUE else FALSE
if ($reference['referenced_column']== 'local_key')
* @return array containing names of other linked
* tables this table has foreign key references to.
array_push($tables, $reference['referenced_table']);
if ($reference['referenced_table']== $tableName)
* returns the name of the column the values of which have
* a foreign key relation with $referredTable
* @param string $referredTable
* @return IF this table has foreign key reference to $referredTable: string representing the column name
if ($reference['referenced_table']== $referredTable)
return $reference['column'];
if ($reference['referenced_table'] == $tableName)
return $reference['cascading_insert'];
if ($reference['referenced_table'] == $tableName)
return $reference['cascading_delete'];
if ($reference['referenced_table'] == $tableName)
return $reference['column'];
//------------------end methods to get information about specific references for this table ------------------
//-------------------------methods to retrieve and convert key values from this table-------------------
return $this->rows_array();
$this->where($field, $operator, $value);
return $this->rows_array();
if ($field <> '' && $operator<> '' && $value<> '')
$this->where($field, $operator, $value);
$result = $this->rows_array();
foreach ($result as $record){
return $this->rows_array();
return $this->rows_array();
* A utility function to retrieve a set of remote_key values based
* on a set of foreign key values that might be referred to by records
* @param rows_array $foreignKeyValues
* the column that is the foreign key
* @return rows_array $remoteKeyValues
* A set of remote_key values for records that have foreign key references
* to any of the values found in $foreignKeyValues.
$this->where($column, 'IN', implode(',', $this->reduce($foreignKeyValues, $isString)));
return $this->rows_array();
* A utility function to retrieve a set of local_key values based
* on a set of foreign key values that might be referred to by records
* @param rows_array $foreignKeyValues
* the column that is the foreign key
* @return rows_array $remoteKeyValues
* A set of remote_key values for records that have foreign key references
* to any of the values found in $foreignKeyValues.
$this->where($column, 'IN', implode(',', $this->reduce($foreignKeyValues, $isString)));
return $this->rows_array();
//---------- end methods to retrieve and convert key values from this table -----------------------
//-----------------------------------------data refresh methods----------------------------------------
* @method refreshLocalData
* @param String $updateEventType - a valid update event type
* as specified in ...... if this argument is not provided
* the default update event is executed
* @todo implement the refresh thing as follows:
* distinguish between the 'refreshType' and the 'updateType'.
* Of 'refreshType' there are two: the 'regular' refresh event
* including only those tables that need refresh regularly; and
* the'all tables' refresh event. Of updateTypes there can be
* any number, one of which is the default update type. The dataset
* to be updated for each of these types is defined in the updatLocalDataRule object.
* @todo the delete and get new events can be combined into one event as the full
* set of keys is returned to the application by the remote server for the delete event.
* based on this it is easy to determine which records are not in the local cache and
* request them from the server.
* @todo think about the implications of deleting records that might be referenced by
* other tables. Deleting records in referenced tables should probably result in some kind
* of update event in table(s) that reference that table as foreign key values in the records
* that refer to records that are now deleted should have been updated in the DoR. In case of an
* ALLDATA update this would be taken care of as a matter of course, but not necessarily in update events
* that work with subsets of the data.
//only if new records are created locally
//This code is here only as a reminder of things to come in future......
//For now there is no implemented funnctionality for writing back new records
//check for auditing results of previously sent new records
//if and process these results locally
//this is yet to be implemented, see method description
//under 'writeNewRecords()' and 'checkAndProcessStagedNewRecords()'methods
//do a data refresh of a fragment as defined by the $updateEventType
* Does a blanket replacement of all local data with data from the DoR
* In future can be made more sophisticated for logging and 'undo' purposes
$this->query("delete from $this->tableName");//truncate $this->tableName
//retrieve the set of DoR key values
//this will be used during delete and insert
//check for deleted Dor records and delete those records locally
//get new records and insert them
//get the full set of key values from the DoR
// $dorKeySet = unserialize($this->serverConnection->getUrlConnection()->getData("getKeySet/$this->tableName/$this->remoteKey/".serialize($this->remoteMap)));
//first select the key values of the records about to be deleted
//we need these values to determine which records in related tables
//would have been affected by deleting these records. There is a possibility that these records
//still exist but have been changed as the records they refer to have been deleted, so they need to be updated in the local cache as well.
//if the referring records in the other table have also been deleted in the DoR there is no problem
//doing this anyway, no records will be updated in that case, and the locally cached records will be deleted during this refresh event.
$whereString = implode(",", $dorKeySet);
$this->where($this->remoteKey,'NOT IN', $whereString);
$delete_records = $this->rows_array();
//now delete the records that no longer exist in the DoR
$this->where($this->remoteKey,'NOT IN', $whereString);
if (count($delete_records)> 0){
//now get the objects that have foreign key references to this table
if (!empty($referringTableObjects)){
foreach ($referringTableObjects as $object)
if ($object->referenceIsCascadingDelete($this->tableName))
//before running the update function in this way, translate/map the $delete_records
//to remote_key values of the referencing table.
$keySet = $object->convertForeignKeyValuesToRemoteKeyValues($delete_records,$object->getForeignKeyFieldName ($this->tableName), $isString);
//and run the update function based on the $delete_records dataset
$object->updateLocalRecords('', $keySet);
* @todo the get new records function should or could
* send a message to the server to get any records from table(s) that
* refer to this table and have a reference to the new record and
* the local cache for this referring table can then be updated accordingly
* @todo split up the 'delete' and 'insert' events, which are currently both
* included in this function.
//get the full set of key values from the DoR
// $dorKeySet = unserialize($this->serverConnection->getUrlConnection()->getData("getKeySet/$this->tableName/$this->remoteKey/".serialize($this->remoteMap)));
//get new records from the DoR
//only if there are any new records to get
if (count($newRecKeys)> 0){
$keyString = implode(",",$newRecKeys);
$where = " where $this->remoteKey IN ($keyString)";
//get the load data remotely - if nothing was specified in the Rule Object, no data will be fetched
//1 if there are tables that refer to this one
//2 for each of those tables:
//3 get the set of remote key values of records that refer to any of the new records off the server
//4 reduce this set of key values to values that exist locally, and run an 'update' event for these records
//1 get the objects that have foreign key references to this table
if (!empty($referringTableObjects)){
foreach ($referringTableObjects as $object)
if ($object->referenceIsCascadingInsert($this->getTableName()))
//get the referring field's name
$referringFieldName = $object->getReferringFieldForTable($this->getTableName());
$tableName = $object->getTableName();
$remoteKey = $object->getRemoteKey();
$keySet = $this->serverConnection->getUrlConnection()->getData("getKeySet/$tableName/$remoteKey/". serialize($object->remoteMap). "/$referringFieldName/$keyString");
//$z = new DataInterFace();
// $keySet = unserialize($z->getKeySet($object->getTableName(),$object->getRemoteKey(),serialize($object->remoteMap),$referringFieldName,$keyString));
$updateKeySet = $object->getKeySetForUpdate($object->getRemoteKey(), 'IN', implode(",",$this->reduce($keySet, $this->remoteKeyIsString)));// array_diff($this->reduce($keySet), $this->reduce($object->getRemoteKeySet()));
//and run the update function based on the $delete_records dataset
$object->updateLocalRecords('', $updateKeySet);
* Enter description here...
* @todo possibly make it optional to include the update rules of referenced tables
* currently this happens without choice. Can be done with a boolean flag in the references table.
* @todo the 'keystring' that is used for the update event has the side effect that
* records that have been deleted from the DoR will be permanently deleted from the cache
* during the update event. So the update event does more than just update. As records that are
* to be deleted are treated differently this should be changed. One way to do this is to get the
* keyset from the server and to trim the 'keystring' down to include only what's actually on the server.
* A current workaround is to do the 'delete' event before the 'update' event.
* @param unknown_type $updateEventType
//if no $keySet was passed in as parameter, generate one first
//retrieve set of keys for records to be updated
//first get the keyset for the records to be updated
//according to the table's own update rule
if (empty($updateEventType)){
$keySet = $rule->applyRule();
$keySet = $rule->applyRule($updateEventType);
//if this table is dependent on other table(s)
//through foreign key references, determine which
//records to be updated according to the update rule(s) for the referenced table(s)
if ($reference['cascading_update'])
$referenced_table = $this->serverConnection->getObject($reference['referenced_table']);
$rule->setORM($referenced_table);
$temp = $rule->applyRule();
//convert this set of remote key values from the
//referenced table to local_key values if this is
//we have to do this because the foreign key values in this table will refer to local key values
$keyIsString = $referenced_table->remoteKeyIsString;
if (!$referenced_table->isSelectOnly()){
$temp = $referenced_table->convertRemoteKeyValuesToLocalKeyValues($temp);
$keyIsString = $referenced_table->primaryKeyIsString;
//finally, get the set of remote key values for any records that
//refer to values in the $referenced_table that we now have in the $temp variable
//and add these to the keyset of records to be refreshed
//lastly, remove any duplicates we might now have in our $keySet
//if we now have a keySet, request a load file from the server
//the load file will contain the records that need to be updated in the local cache
$where = " where $this->remoteKey IN ($keyString)";
//get the load data remotely - if nothing was specified in the Rule Object, no data will be fetched
//if $fileString is empty it means that either the keyset was empty ie nothing to update
//or remotely all records were deleted, in which case the 'DeleteLocalRecords' method will
if (!empty($fileString) ){
//if this is a table with local primary key values
//insert these values into the load file
//if this is a table with references to local key values in other tables
//do the re-key thing now
//delete the records that should be updated
//in future these should be written to a log file
//which should allow for a roll back of the whole event
//we don't want to locally delete any records that no longer exist on the server
//at this point as we want to do this through the delete method whenever that runs
//that's why we're going to make sure that we delete only those records that are actually in the load file
$this->where($this->remoteKey, 'IN', "$keyStringBasedOnLoadFile");
//and load the new records to the table
* @method checkAndProcessStagedNewRecords
* @todo Implement this method, and its counterpart on the server side as follows:
* This method checks log files on the server side that report
* the results of auditing previously submitted new records.
* To this end the server side should implement a system of making the
* relevant log files downloadable by clients who submitted new records.
* The result of auditing is either 'pass', in which cas the log file will
* contain the remote key value for the record, or 'fail' in which case the
* new record was refused for some reason. In the case of 'fail' there should
* probably be a system of codes that indicate the reason, eg 'duplicate record' or something.
//----------------------------end data refresh methods -------------------------------------
//--------------------------------------methods to maniuplate loadfile strings---------------------------
//first get the remote to lcal key mapping
$records = explode("\n",$fileString);
$extractedValues = array();
foreach ($records as $record){
* @method _replaceValuesInLoadFileString
* This method replaces values in a string in the manner as follows
* from the description of the parameters
* @param string $fileString:
* representing the loadfile, tab delimited field values, newline delimited records
* @param Assoc Array $replaceValues:
* key is lookup value, value is replace value
* @param integer $lookupColumn:
* column number that's the reference in the record to find the replace value
* @param integer $replaceColumn
* column number in the record the value of which should be replaced
* @return the $fileString with the $replaceColum updated with the replace value as found in
* the $replaceValues array.
//1 explode filestring into an array of strings
$records = explode("\n",$fileString);
foreach ($records as $record){
//to cover for the event that the load file has records that
//cannot be resolved in the $replaceValues array
//(can be caused by data corruption in terms of referential integrity
//, or by the fact that the load file contains records that do not exist yet
//in the local cache, which can happen in the all data refresh event)
if (isset ($replaceValues[$x[$lookupColumn]]))
$x[$replaceColumn]= $replaceValues[$x[$lookupColumn]];
$fileString = implode("\n", $newRecs);
//get the value pairs for local/remote keys from the
$keys = $object->getLocalRemoteKeyPairs(TRUE);
$this->query("Load data infile '". PATH_LOADFILES. "/$this->tableName.txt'
into table $this->tableName CHARACTER SET utf8");
//$x = new DataInterface();
//$_POST['var1']= serialize($keySet);
// return $x->getLoadFile($this->tableName,serialize($this->remoteMap));
//--------------------------------------end methods to maniuplate loadfile strings---------------------------
//------------------------------------------Utility Methods---------------------------------------------------
* utility function to reduce an array of arrays to a 'simple' array
* that is: each value in the returned $values array is not an array but one string value
* if the second dimension array contains multiple values these will be imploded to
* a comma delimited string
* @param two dimensional array $data
* @return one dimensional array $values
private function reduce($data, $AddQuotes = FALSE)
foreach ($data as $value)
array_walk($values, array($this, 'surroundValues'), "'");
$item1 = "$char$item1$char";
|