<?php
/**
* An Implementation of the abstract class Session. The mapping is based on XML file.
*
* @package com.freeorm
* @author Yide Zou
* @link http://www.freeorm.com
* @copyright Copyright (c) 2010 Yide Zou <hide@address.com>.
* All Rights Reserved.
* @license This software is released under the terms of the GNU Lesser General Public License
* A copy of which is available from http://www.gnu.org/copyleft/lesser.html
*/
require_once 'Session.php';
require_once 'EntityProxy.php';
class SessionXML extends Session
{
// xml orm
private $cfg = null;
/**
* parse the xml data,
* set the properties, the component object and many-to-one relation,
* the column and value will be saved into parameter arrays
* @param $objclone, the obj in cache, instead of XX-to-XX object, it use EntityProxy
*/
private function saveClassProp($classNode, $obj, $objclone, &$arrCol, &$arrVal)
{
/* check the basic properties */
foreach ($classNode->property as $property)
{
// skip the property with the formular attribute,
// they are only useful with SELECT operation
if ((string)$property['formular'] != '')
continue;
// skip the property with the insert='false'
if ((string)$property['insert'] == 'false')
continue;
$fieldname = (string)$property['name'];
$value = $obj->$fieldname;
$objclone->$fieldname = $value;
if (is_string($value))
$value = "'$value'";
$arrVal[] = $value;
// set default column name to the property's name
$column = (string)$property['column'] == '' ? $fieldname : (string)$property['column'];
$arrCol[] = $this->db->sqlquote($column);
}
/* check the properties from the normal(not collection) component class */
foreach ($classNode->component as $component)
{
$compFieldname = (string)$component['name'];
$compClassname = (string)$component['class'];
$objclone->$compFieldname = new $compClassname();
foreach ($component->property as $property)
{
// skip the property with the formular attribute, they are only useful with SELECT operation
if ((string)$property['formular'] != '')
continue;
// skip the property with the insert='false'
if ((string)$property['insert'] == 'false')
continue;
$fieldname = (string)$property['name'];
$value = $obj->$compFieldname->$fieldname;
$objclone->$compFieldname->$fieldname = $value;
if (is_string($value))
$value = "'$value'";
$arrVal[] = $value;
// set default column name to the property's name
$column = (string)$property['column'] == '' ? $fieldname : (string)$property['column'];
$arrCol[] = $this->db->sqlquote($column);
}
}
/* check the many-to-one relation */
foreach ($classNode->{'many-to-one'} as $m2o)
{
$fieldname = (string)$m2o['name'];
$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
$arrCol[] = $this->db->sqlquote($fkcolumn);
$oneClassName = (string)$m2o['class'];
//TODO: only works for hierarchy per class inherate strategy
$oneClassNode = $this->getRootClassNode($oneClassName);
$pkfield = (string)$oneClassNode->id['name'];
$value = $obj->$fieldname->$pkfield;
$objclone->$fieldname = new EntityProxy($this, $oneClassName, $value);
if (is_string($value))
$value = "'$value'";
$arrVal[] = $value;
}
}
/**
* Help funtion of saving/reading Component Collection,
* parse the component tag inside collection tags
* and get the fieldnames and columns array
* @param $component, the Node object
* @param $compFieldnames, the reference of the array of fieldnames
* @param $columns, the reference of the array of columns
* @param $withquote, boolean, whether the $columns with ``
*/
private function parseComponentCollection($component, &$compFieldnames, &$columns, $withquote=true)
{
// read the fieldnames and columns of component tag
foreach ($component->property as $property)
{
// skip the property with the formular attribute, they are only useful with SELECT operation
if ((string)$property['formular'] != '')
continue;
// skip the property with the insert='false'
if ((string)$property['insert'] == 'false')
continue;
$compFieldname = (string)$property['name'];
$compFieldnames[] = $compFieldname;
// set default column name to the property's name
$column = (string)$property['column'] == '' ? $compFieldname : (string)$property['column'];
if ($withquote)
$column = $this->db->sqlquote($column);
$columns[] = $column;
}
}
/**
* Parse the xml and save the collections into own table
* It won't change the owner's table, but it need the id of owner,
* so it should be called after the mainly insert operation of owner.
*
* For the one-to-many relation, if the many side objects are not yet saved,
* It just do nothing, won't throw exceptions.
* Because it just update the foreign key of the many side table.
* @param $classNode
* @param $obj
* @param $objclone the $cacheobj, instead of xx-to-xx relation object, the linked Entities will be replaced by EntityProxy
* @param $ownerid the id of owner object
* @param $caller
* @param $callerid they are useful for the many-to-many relation saving
*/
private function saveClassCollection($classNode, $obj, $objclone, $ownerid)
{
// set
foreach ($classNode->set as $set)
{
$fieldname = (string)$set['name'];
// set table name same to the fieldname by default
$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$set->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$set->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $set->component;
$o2m = $set->{'one-to-many'};
$m2m = $set->{'many-to-many'};
/* save the elements */
if ($elementname)
{
$arrclone = array();
foreach ($obj->$fieldname as $value)
{
$arrclone[] = $value;
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($ownerid, $value)";
$this->db->query($sql);
}
$objclone->$fieldname = $arrclone;
}
/* save the components */
elseif ($component)
{
$compClassName = (string)$component['class'];
$arrclone = array();
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($obj->$fieldname as $comp)
{
$compClone = new $compClassName();
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
$compClone->$compFieldname = $value;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($ownerid, $values)";
$this->db->query($sql);
$arrclone[] = $compClone;
}
$objclone->$fieldname = $arrclone;
}
/* save the one-to-many relation */
/* if the many side objects not yet be saved, nothing happens */
elseif ($o2m)
{
$manyClassName = (string)$o2m['class'];
//TODO: only works for hierarchy per class inherate strategy
$manyClassNode = $this->getRootClassNode($manyClassName);
$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$manyClassNode->id['name'];
$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
$ids = array();
$arrclone = array();
foreach ($obj->$fieldname as $mObj)
{
$id = $mObj->$pkfield;
// the many side object is new
if ($id === null)
{
// cascade save
if ( strpos( (string)$set['cascade'], 'save-update') !== false )
$id = $this->save($mObj);
else
continue;
}
else
{
// deattached object, then make it attached
if (! $this->inCache((string)$manyClassNode['name'], $id, true) )
$this->update($mObj, $id);
}
$arrclone[] = new EntityProxy($this, $manyClassName, $id);
if (is_string($id))
$id = "'$id'";
$ids[] = $id;
}
if (count($ids)>0)
{
$ids = implode(',', $ids);
$sql = "UPDATE $tablename SET $keyname=$ownerid WHERE $pkname in ($ids)";
$this->db->query($sql);
}
$objclone->$fieldname = $arrclone;
}
/* save the many-to-many relation */
elseif ($m2m)
{
$manyClassName = (string)$m2m['class'];
//TODO: only works for hierarchy per class inherate strategy
$manyClassNode = $this->getRootClassNode($manyClassName);
$pkname = (string)$m2m['column'];
$pkname = $this->db->sqlquote($pkname);
$pkfield = (string)$manyClassNode->id['name'];
$arrclone = array();
foreach ($obj->$fieldname as $mObj)
{
$id = $mObj->$pkfield;
// the many side object is new
if ($id === null)
{
// cascade save
if ( strpos( (string)$set['cascade'], 'save-update') !== false )
$id = $this->save($mObj);
else
continue;
}
else
{
// if it's the inverse side of many-to-many relation,
// then the $id is its party object id, we dont need more check
// if it's not the inverse side, then check the deattached state
if ( (string)$set['inverse'] !== 'true' )
{
// deattached object, then make it attached
if (! $this->inCache((string)$manyClassNode['name'], $id, true) )
$this->update($mObj, $id);
}
}
$arrclone[] = new EntityProxy($this, $manyClassName, $id);
// prevent duplicated saving for the inverse side
if ( (string)$set['inverse'] !== 'true' )
{
if (is_string($id))
$id = "'$id'";
$sql = "INSERT INTO $tablename ($keyname, $pkname) VALUES($ownerid, $id)";
$this->db->query($sql);
}
}
$objclone->$fieldname = $arrclone;
}
}
// bag
foreach ($classNode->bag as $bag)
{
$fieldname = (string)$bag['name'];
// set table name same to the fieldname by default
$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
$tablename = $this->db->sqlquote($tablename);
$pkname = (string)$bag->id['column'];
$pkname = $this->db->sqlquote($pkname);
$keyname = (string)$bag->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$bag->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $bag->component;
/* save the elements */
if ($elementname)
{
$arrclone = array();
foreach ($obj->$fieldname as $value)
{
$arrclone[] = $value;
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($pkname, $keyname, $elementname) VALUES (NULL, $ownerid, $value)";
$this->db->query($sql);
}
$objclone->$fieldname = $arrclone;
}
/* save the components */
elseif ($component)
{
$compClassName = (string)$component['class'];
$arrclone = array();
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($obj->$fieldname as $comp)
{
$compClone = new $compClassName();
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
$compClone->$compFieldname = $value;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($pkname, $keyname, $columns) VALUES (NULL, $ownerid, $values)";
$this->db->query($sql);
$arrclone[] = $compClone;
}
$objclone->$fieldname = $arrclone;
}
}
// map, the key of the map must be type string
foreach ($classNode->map as $map)
{
$fieldname = (string)$map['name'];
// set table name same to the fieldname by default
$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$map->key['column'];
$keyname = $this->db->sqlquote($keyname);
$mapkeyname = (string)$map->{'map-key'}['column'];
$mapkeyname = $this->db->sqlquote($mapkeyname);
$elementname = (string)$map->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $map->component;
/* save the elements */
if ($elementname)
{
$arrclone = array();
foreach ($obj->$fieldname as $key=>$value)
{
$arrclone[$key] = $value;
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $elementname) VALUES ($ownerid, '$key', $value)";
$this->db->query($sql);
}
$objclone->$fieldname = $arrclone;
}
/* save the components */
elseif ($component)
{
$compClassName = (string)$component['class'];
$arrclone = array();
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($obj->$fieldname as $key=>$comp)
{
$compClone = new $compClassName();
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
$compClone->$compFieldname = $value;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $columns) VALUES ($ownerid, '$key', $values)";
$this->db->query($sql);
$arrclone[$key] = $compClone;
}
$objclone->$fieldname = $arrclone;
}
}
}
private function deleteClassCollection($classNode, $ownerid, $obj)
{
// set
foreach ($classNode->set as $set)
{
$fieldname = (string)$set['name'];
// set table name same to the fieldname by default
$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$set->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$set->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $set->component;
$o2m = $set->{'one-to-many'};
$m2m = $set->{'many-to-many'};
/* delete the elements or components */
if ($elementname || $component)
{
$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
$this->db->query($sql);
}
/* delete the one-to-many relation */
elseif ($o2m && strpos((string)$set['cascade'], 'delete')!==false)
{
foreach ($obj->$fieldname as $mObj)
$this->delete($mObj);
}
/* delete the many-to-many relation */
elseif ($m2m)
{
if ( strpos((string)$set['cascade'], 'delete')!==false && (string)$set['inverse']!=='true' )
{
foreach ($obj->$fieldname as $mObj)
$this->delete($mObj);
}
// delete the relation link table
$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
$this->db->query($sql);
}
}
// bag
foreach ($classNode->bag as $bag)
{
$fieldname = (string)$bag['name'];
// set table name same to the fieldname by default
$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$bag->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$bag->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $bag->component;
if ($elementname || $component)
{
$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
$this->db->query($sql);
}
}
// map, the key of the map must be type string
foreach ($classNode->map as $map)
{
$fieldname = (string)$map['name'];
// set table name same to the fieldname by default
$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$map->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$map->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $map->component;
if ($elementname || $component)
{
$sql = "DELETE FROM $tablename WHERE $keyname=$ownerid";
$this->db->query($sql);
}
}
}
/**
* Check the xml tag, if it's the root class
* @param object, a reference of the xml tag object
* @return boolean, true if it's root
*/
private function isRootClassNode($classNode)
{
//root has no joined and discriminator-value attribute.
return (string)$classNode['joined']=='' && (string)$classNode['discriminator-value']=='';
}
/**
* Parse the xml, read the date from database and save them into object
* include normal properties, collections, relations
* @param $obj, has no linked object, they will be replaced with DummyLinkedEntity. (objcache)
* @param $obj2, is the real object with full links (objwatcher)
* @param $id, int or string, if it's a string, it includes the '' quotation
* @return false, if no record found, (wrong id), otherweise return true
*/
private function readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id)
{
/* read the properties */
$columns = array();
$properties = array();
foreach ($classNode->property as $property)
{
$propName = (string)$property['name'];
$properties[] = $propName;
// property with the formular attribute
if ((string)$property['formular'] != '')
$columns[] = (string)$property['formular'];
else
{
// set default column name to the property's name
$column = (string)$property['column'] == '' ? $propName : (string)$property['column'];
$columns[] = $this->db->sqlquote($column);
}
}
if ( count($columns)>0 )
{
$sql = "SELECT {$columns[0]} AS {$properties[0]}";
for ($i=1; $i<count($columns); $i++)
{
$sql .= ",{$columns[$i]} AS {$properties[$i]}";
}
$sql .= " FROM $tablename WHERE $pkname=$id";
$result = $this->db->loadObject($sql);
// not found, wrong id
if ($result === null)
return false;
foreach ($properties as $prop)
{
$obj->$prop = $result->$prop;
$obj2->$prop = $result->$prop;
}
}
/* read the many-to-one relation */
$columns = array();
$properties = array();
$classnames = array();
foreach ($classNode->{'many-to-one'} as $m2o)
{
$fieldname = (string)$m2o['name'];
$properties[] = $fieldname;
$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
// without sql quote, we need them later.
$columns[] = $fkcolumn;
$classname = (string)$m2o['class'];
$classnames[] = $classname;
}
if ( count($columns)>0 )
{
$sql = "SELECT `{$columns[0]}`";
for ($i=1; $i<count($columns); $i++)
{
$sql .= ",`{$columns[$i]}`";
}
$sql .= " FROM $tablename WHERE $pkname=$id";
$result = $this->db->loadObject($sql);
// not found, wrong id
if ($result === null)
return false;
for ($i=0; $i<count($columns); $i++)
{
$pkvalue = $result->$columns[$i];
// FIXME: is_numeric() return true for string like '3.12' and '2e12'
if (is_numeric($pkvalue))
$pkvalue = (int)$pkvalue;
$ep1 = new EntityProxy($this, $classnames[$i], $pkvalue);
$ep2 = new EntityProxy($this, $classnames[$i], $pkvalue);
$obj->$properties[$i] = $ep1;
$obj2->$properties[$i] = $ep2;
// save the EntityProxy into cache
$this->insertCache($ep1, $ep2, $classnames[$i], $pkvalue, false);
}
}
/* read collections */
// set
foreach ($classNode->set as $set)
{
$fieldname = (string)$set['name'];
// set table name same to the fieldname by default
$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$set->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$set->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $set->component;
$o2m = $set->{'one-to-many'};
$m2m = $set->{'many-to-many'};
$orderby = (string)$set['order-by'];
if ($orderby)
$orderby = "ORDER BY $orderby";
$resultArr = array();
$resultArr2 = array();
/* elements */
if ($elementname)
{
$sql = "SELECT $elementname AS element FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$resultArr[] = $result->element;
$resultArr2[] = $result->element;
}
}
/* components */
elseif ($component)
{
$classname = (string)$component['class'];
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns, false);
$columnsStr = "`{$columns[0]}`";
for ($i=1; $i<count($columns); $i++)
$columnsStr .= ",`{$columns[$i]}`";
$sql = "SELECT $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$compObj = new $classname();
$compObj2 = new $classname();
for ($i = 0; $i<count($compFieldnames); $i++)
{
$compFieldname = $compFieldnames[$i];
$column = $columns[$i];
$compObj->$compFieldname = $result->$column;
$compObj2->$compFieldname = $result->$column;
}
$resultArr[] = $compObj;
$resultArr2[] = $compObj2;
}
}
/* read the one-to-many relation */
elseif ($o2m)
{
$manyClassName = (string)$o2m['class'];
$manyClassNode = $this->getRootClassNode($manyClassName);
$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$manyClassNode->id['name'];
$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
$sql = "SELECT `$pkname` FROM $tablename WHERE $keyname=$id";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$pkvalue = $result->$pkname;
// FIXME: is_numeric() return true for string like '3.12' and '2e12'
if (is_numeric($pkvalue))
$pkvalue = (int)$pkvalue;
$ep1 = new EntityProxy($this, $manyClassName, $pkvalue);
$ep2 = new EntityProxy($this, $manyClassName, $pkvalue);
$resultArr[] = $ep1;
$resultArr2[] = $ep2;
// save the EntityProxy into cache
$this->insertCache($ep1, $ep2, (string)$manyClassNode['name'], $pkvalue, true);
}
}
/* read the many-to-many relation */
elseif ($m2m)
{
$manyClassName = (string)$m2m['class'];
$manyClassNode = $this->getRootClassNode($manyClassName);
$tablename = (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$manyClassNode->id['name'];
$pkname = (string)$m2m['column'];
$sql = "SELECT `$pkname` FROM $tablename WHERE $keyname=$id";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$pkvalue = $result->$pkname;
// FIXME: is_numeric() return true for string like '3.12' and '2e12'
if (is_numeric($pkvalue))
$pkvalue = (int)$pkvalue;
$ep1 = new EntityProxy($this, $manyClassName, $pkvalue);
$ep2 = new EntityProxy($this, $manyClassName, $pkvalue);
$resultArr[] = $ep1;
$resultArr2[] = $ep2;
// save the EntityProxy into cache
$this->insertCache($ep1, $ep2, (string)$manyClassNode['name'], $pkvalue, true);
}
}
$obj->$fieldname = $resultArr;
$obj2->$fieldname = $resultArr2;
}
// bag
foreach ($classNode->bag as $bag)
{
$fieldname = (string)$bag['name'];
// set table name same to the fieldname by default
$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
$tablename = $this->db->sqlquote($tablename);
//$pkname = (string)$bag->id['column'];
$keyname = (string)$bag->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$bag->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $bag->component;
$orderby = (string)$bag['order-by'];
if ($orderby)
$orderby = "ORDER BY $orderby";
$resultArr = array();
$resultArr2 = array();
/* elements */
if ($elementname)
{
$sql = "SELECT $elementname AS element FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$resultArr[] = $result->element;
$resultArr2[] = $result->element;
}
}
/* components */
elseif ($component)
{
$className = (string)$component['class'];
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns, false);
$columnsStr = "`{$columns[0]}`";
for ($i=1; $i<count($columns); $i++)
$columnsStr .= ",`{$columns[$i]}`";
$sql = "SELECT $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$compObj = new $className();
$compObj2 = new $className();
for ($i = 0; $i<count($compFieldnames); $i++)
{
$compFieldname = $compFieldnames[$i];
$column = $columns[$i];
$compObj->$compFieldname = $result->$column;
$compObj2->$compFieldname = $result->$column;
}
$resultArr[] = $compObj;
$resultArr2[] = $compObj2;
}
}
$obj->$fieldname = $resultArr;
$obj2->$fieldname = $resultArr2;
}
// map, the key of the map must be type string
foreach ($classNode->map as $map)
{
$fieldname = (string)$map['name'];
// set table name same to the fieldname by default
$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$map->key['column'];
$keyname = $this->db->sqlquote($keyname);
$mapkeyname = (string)$map->{'map-key'}['column'];
$mapkeyname = $this->db->sqlquote($mapkeyname);
$elementname = (string)$map->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $map->component;
$orderby = (string)$map['order-by'];
if ($orderby)
$orderby = "ORDER BY $orderby";
$resultArr = array();
$resultArr2 = array();
/* elements */
if ($elementname)
{
// becareful, key and value are keyword of MySQL, so here use res_ prefix
$sql = "SELECT $mapkeyname AS res_key, $elementname AS res_value FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$resultArr[$result->res_key] = $result->res_value;
$resultArr2[$result->res_key] = $result->res_value;
}
}
/* components */
elseif ($component)
{
$className = (string)$component['class'];
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns, false);
$columnsStr = "`{$columns[0]}`";
for ($i=1; $i<count($columns); $i++)
$columnsStr .= ",`{$columns[$i]}`";
$sql = "SELECT $mapkeyname AS res_key, $columnsStr FROM $tablename WHERE $keyname=$id $orderby";
$results = $this->db->loadObjectList($sql);
foreach ($results as $result)
{
$compObj = new $className();
$compObj2 = new $className();
for ($i = 0; $i<count($compFieldnames); $i++)
{
$compFieldname = $compFieldnames[$i];
$column = $columns[$i];
$compObj->$compFieldname = $result->$column;
$compObj2->$compFieldname = $result->$column;
}
$resultArr[$result->res_key] = $compObj;
$resultArr2[$result->res_key] = $compObj2;
}
}
$obj->$fieldname = $resultArr;
$obj2->$fieldname = $resultArr2;
}
return true;
}
/**
* Get the root class node
* @param String, the current class name
* @return Object, the root class node object
*/
private function getRootClassNode($classname)
{
$rootClassNode = $this->cfg->xpath("//class[@name='$classname']/ancestor-or-self::*[parent::mappings]");
return $rootClassNode[0];
}
public function __construct($db, $logger, $cfg)
{
parent::__construct($db, $logger);
$this->cfg = $cfg;
}
/**
* Delete the object from database
* @param $obj
* @return the id of the object
*/
protected function doDelete($obj)
{
$classname = $this->getObjClassname($obj);
$id = $this->getObjId($obj);
$classNode = $this->cfg->xpath("//class[@name='$classname']");
$classNode = $classNode[0];
// found the root? if not then delete the collection properties from bottom to up
while (!$this->isRootClassNode($classNode))
{
$this->deleteClassCollection($classNode, $id, $obj);
// look other level of the class hierarchy
// default: joined=false, use discriminator inherit strategy
if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
{
$parentClassname = get_parent_class($classname);
$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
$parentClassNode = $parentClassNode[0];
// recursiv lookup
$classNode = $parentClassNode;
$classname = $parentClassname;
}
}
// the root class
$this->deleteClassCollection($classNode, $id, $obj);
/*
* Delete the basic class table,
* we just skip the many-to-one relation
*/
$tablename = (string)$classNode['table'] == '' ? $classname : (string)$classNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$classNode->id['name'];
$pkname = (string)$classNode->id['column'] == '' ? $pkfield : (string)$classNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
$sql = "DELETE FROM $tablename WHERE $pkname=$id";
$this->db->query($sql);
// make the delete real happen
$this->flush();
return $id;
}
/**
* Save the object into database and in cache
* @param $obj
* @return last inserted id
*/
protected function doSave($obj)
{
$columns = array();
$values = array();
$classname = get_class($obj);
// make a copy of it, we need it later.
$_classname = $classname;
// the cache object, which use EntityProxy
$objcache = new $classname();
$classNode = $this->cfg->xpath("//class[@name='$classname']");
$classNode = $classNode[0];
$_classNode = clone $classNode;
/*
* Saving basic properties
*/
$this->logger->log('saving basic properties');
// found the root? if not then save the basic properties from bottom to up
while (!$this->isRootClassNode($classNode))
{
$this->saveClassProp($classNode, $obj, $objcache, $columns, $values);
// save other level of the class hierarchy
// default: joined=false, use discriminator inherit strategy
if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
{
$parentClassname = get_parent_class($classname);
$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
$parentClassNode = $parentClassNode[0];
// if the discriminator tag with a formular attribute,
// then wont save the discriminator-value into database
if ((string)$parentClassNode->discriminator['formular'] == '')
{
$columns[] = $this->db->sqlquote( (string)$parentClassNode->discriminator['column'] );
$values[] = "'".(string)$classNode['discriminator-value']."'";
}
// recursiv lookup
$classNode = $parentClassNode;
$classname = $parentClassname;
}
}
// the root class
$this->saveClassProp($classNode, $obj, $objcache, $columns, $values);
$colums_str = implode(",", $columns);
$values_str = implode(",", $values);
// set table name same to the classname by default
$tablename = (string)$classNode['table'] == '' ? $classname : (string)$classNode['table'];
$tablename = $this->db->sqlquote($tablename);
// set default primary key column name to the class identify property's name
$pkfield = (string)$classNode->id['name'];
$pkname = (string)$classNode->id['column'] == '' ? $pkfield : (string)$classNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
$sql = "INSERT INTO $tablename ($pkname, $colums_str) VALUES (NULL, $values_str)";
$this->db->query($sql);
// make the insert real happen
$this->flush();
// set the lacked id of $obj to the auto generated primary key
$objid = $this->db->getInsertId();
$obj->$pkfield = $objid;
$objcache->$pkfield = $objid;
/*
* saving the collections like set/bag/map of classes
*/
$this->logger->log('saving the collections');
$classname = $_classname;
$classNode = clone $_classNode;
// found the root? if not then save the collections from bottom to up
while (!$this->isRootClassNode($classNode))
{
$this->saveClassCollection($classNode, $obj, $objcache, $objid);
// save other level of the class hierarchy
// default: joined=false, use discriminator inherit strategy
if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
{
$parentClassname = get_parent_class($classname);
$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
$parentClassNode = $parentClassNode[0];
// recursiv lookup
$classNode = $parentClassNode;
$classname = $parentClassname;
}
}
// the root class
$this->saveClassCollection($classNode, $obj, $objcache, $objid);
//TODO: maybe we should not make this flush? we just need a insert_id
$this->flush();
// save it into cache
$this->insertCache($objcache, $obj, $classname, $objid, true);
return $objid;
}
/*
* Override
*/
protected function getRootClassname($classname)
{
$classNode = $this->getRootClassNode($classname);
if ($classNode !== null)
return (string)$classNode['name'];
else
return null;
}
/**
* Read an object from database.
* @param $classname: the assumed return class type, the actual class type depends on the saved object
* @param $id: the id of the object
* @return an array of two object,
* they have no real linked object, it will be replaced by DummyLinkedEntity.
* it's polymorph, get_class($obj) will return the true class name, the subclass.
*/
protected function doGet($classname, $id)
{
// add quote if $id is a string
if (is_string($id))
$id = "'$id'";
// find the root classNode,
// we need it, because for the "hierarchy per table" inherate strategy, the database table information is saved there.
$rootClassNode = $this->getRootClassNode($classname);
// set table name, pkname and idprop from the xml root class
$tablename = (string)$rootClassNode['table'] == '' ? (string)$rootClassNode['name'] : (string)$rootClassNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkname = (string)$rootClassNode->id['column'] == '' ? (string)$rootClassNode->id['name'] : (string)$rootClassNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
$idprop = (string)$rootClassNode->id['name'];
/*
* From top to bottom search the xml, set the $classNode and $classname to the actual object class
* We cannot direct use the classname from the parameter of this function, because it can be the superclass. (Polymorph)
*/
$classNode = $rootClassNode;
// has subclass?
while ((string)$classNode->class != '')
{
$discriminator = $classNode->discriminator;
// property with the formular attribute
if ((string)$discriminator['formular'] != '')
$column = (string)$discriminator['formular'];
else
$column = $this->db->sqlquote( (string)$discriminator['column'] );
$sql = "SELECT $column AS discriminatorVar FROM $tablename WHERE $pkname=$id";
$discriminatorVar = $this->db->loadObject($sql)->discriminatorVar;
// discriminatorVar==null means, there is subclass xml defination,
// but the object is the instance of the superclass
// we found the object, break
if ($discriminatorVar == null)
break;
// recursive
$classNode = $classNode->xpath("class[@discriminator-value='$discriminatorVar']");
$classNode = $classNode[0];
}
// the sub object which we expect
$classname = (string)$classNode['name'];
$obj = new $classname();
$obj2 = new $classname();
$obj->$idprop = $id;
$obj2->$idprop = $id;
// from bottom to to search the xml, set the values to object
// found the root?
while (!$this->isRootClassNode($classNode))
{
// set the properties of the entity class
// if no record found, that means wrong id
if (!$this->readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id))
return null;
// set the properties of the component class
foreach ($classNode->component as $component)
{
$compFieldname = (string)$component['name'];
$compClassname = (string)$component['class'];
$compObj = new $compClassname();
$compObj2 = new $compClassname();
$this->readClassProp($component, $compObj, $compObj2, $tablename, $pkname, $id);
$obj->$compFieldname = $compObj;
$obj2->$compFieldname = $compObj2;
}
// read other level of the class hierarchy
// default: joined=false, use discriminator inherit strategy
if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
{
$parentClassname = get_parent_class($classname);
$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
$parentClassNode = $parentClassNode[0];
// recursiv lookup
$classNode = $parentClassNode;
$classname = $parentClassname;
}
}
// the properties of the root class
// if no record found, that means wrong id
if (!$this->readClassProp($classNode, $obj, $obj2, $tablename, $pkname, $id))
return null;
// set the properties of the component class
foreach ($classNode->component as $component)
{
$compFieldname = (string)$component['name'];
$compClassname = (string)$component['class'];
$compObj = new $compClassname();
$compObj2 = new $compClassname();
$this->readClassProp($component, $compObj, $compObj2, $tablename, $pkname, $id);
$obj->$compFieldname = $compObj;
$obj2->$compFieldname = $compObj2;
}
return array($obj, $obj2);
}
/**
* Get the id of the object, the object can be an EntityProxy or a real object
* @param $obj
* @return null if the object cannot be found in database
*/
public function getObjId($obj)
{
if ($obj instanceof EntityProxy)
return $obj->id;
$rootClassNode = $this->getRootClassNode(get_class($obj));
// not found
if ($rootClassNode == null)
return null;
$idprop = (string)$rootClassNode->id['name'];
return $obj->$idprop;
}
/**
* Compare the objcache and objwatcher
* update the changes to database
*/
protected function doDirtyCheck($objcache, $objwatcher)
{
// the get_class() always return the actual class name,
// so we dont need to care about polymorph
$classname = $this->getObjClassname($objcache);
// find the root classNode,
// we need it, because for the "hierarchy per table" inherate strategy, the database table information is saved there.
$rootClassNode = $this->getRootClassNode($classname);
// set table name, pkname and idprop from the xml root class
$tablename = (string)$rootClassNode['table'] == '' ? (string)$rootClassNode['name'] : (string)$rootClassNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkname = (string)$rootClassNode->id['column'] == '' ? (string)$rootClassNode->id['name'] : (string)$rootClassNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
$idprop = (string)$rootClassNode->id['name'];
$id = $objcache->$idprop;
// add quote if $id is a string
if (is_string($id))
$id = "'$id'";
// the class node of the actual class
$classNode = $this->cfg->xpath("//class[@name='$classname']");
$classNode = $classNode[0];
// from bottom to up to search the xml, set the values to object
// found the root?
while (!$this->isRootClassNode($classNode))
{
// check the properties of the entity class
$this->checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id);
// check the properties of the component class
foreach ($classNode->component as $component)
{
$compFieldname = (string)$component['name'];
$this->checkClassProp($component, $objcache->$compFieldname, $objwatcher->$compFieldname, $tablename, $pkname, $id);
}
// read other level of the class hierarchy
// default: joined=false, use discriminator inherit strategy
if ( (string)$classNode['joined'] == 'false' || (string)$classNode['joined'] == '' )
{
$parentClassname = get_parent_class($classname);
$parentClassNode = $this->cfg->xpath("//class[@name='$parentClassname']");
$parentClassNode = $parentClassNode[0];
// recursiv lookup
$classNode = $parentClassNode;
$classname = $parentClassname;
}
}
// the properties of the root class
$this->checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id);
// check the properties of the component class
foreach ($classNode->component as $component)
{
$compFieldname = (string)$component['name'];
$this->checkClassProp($component, $objcache->$compFieldname, $objwatcher->$compFieldname, $tablename, $pkname, $id);
}
}
private function checkClassProp($classNode, $objcache, $objwatcher, $tablename, $pkname, $id)
{
// both are same EntityProxy and still not be "touched", no need more check.
if ($objcache instanceof EntityProxy && $objcache->equalsAndUnloaded($objwatcher))
return true;
/* check the properties */
$columns = array();
$properties = array();
foreach ($classNode->property as $property)
{
// property with the formular attribute, it's read only
if ((string)$property['formular'] != '')
continue;
else
{
$propName = (string)$property['name'];
// record the different columns
if ($objcache->$propName != $objwatcher->$propName)
{
$value = $objwatcher->$propName;
if (is_string($value))
$value = "'$value'";
$properties[] = $value;
// set default column name to the property's name
$column = (string)$property['column'] == '' ? $propName : (string)$property['column'];
$columns[] = $this->db->sqlquote($column);
}
}
}
if ( count($columns)>0 )
{
$sql = "UPDATE $tablename SET {$columns[0]} = {$properties[0]}";
for ($i=1; $i<count($columns); $i++)
{
$sql .= ",{$columns[$i]} = {$properties[$i]}";
}
$sql .= " WHERE $pkname=$id";
$this->db->query($sql);
}
/* check the many-to-one relation */
$columns = array();
$properties = array();
foreach ($classNode->{'many-to-one'} as $m2o)
{
$fieldname = (string)$m2o['name'];
if (! $objcache->$fieldname->equals($objwatcher->$fieldname) )
{
$fkcolumn = (string)$m2o['column'] == '' ? $fieldname : (string)$m2o['column'];
$columns[] = $this->db->sqlquote($fkcolumn);
$watcher = $objwatcher->$fieldname;
$watcherId = $this->getObjId($watcher);
$watcherClassname = $this->getObjClassname($watcher);
// id == null, that means it's a new entity
if ($watcherId === null)
$watcherId = $this->save($watcher);
// deattached object, then make it attached, in order to dirty check it later.
if (! $this->inCache($watcherClassname, $watcherId, false) )
{
$this->update($watcher, $watcherId);
// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
// so we should do the dirty check at once
$this->doDirtyCheck($this->objcache[$this->getRootClassname($watcherClassname)][$watcherId], $watcher);
}
$properties[] = $watcherId;
}
}
if ( count($columns)>0 )
{
$sql = "UPDATE $tablename SET {$columns[0]} = {$properties[0]}";
for ($i=1; $i<count($columns); $i++)
{
$sql .= ",{$columns[$i]} = {$properties[$i]}";
}
$sql .= " WHERE $pkname=$id";
$this->db->query($sql);
}
/* check collections */
// set
foreach ($classNode->set as $set)
{
$fieldname = (string)$set['name'];
// set table name same to the fieldname by default
$tablename = (string)$set['table'] == '' ? $fieldname : (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$set->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$set->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $set->component;
$o2m = $set->{'one-to-many'};
$m2m = $set->{'many-to-many'};
/* elements */
if ($elementname)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, because no primary key information, just delete and insert again.
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
foreach ($objwatcher->$fieldname as $value)
{
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($id, $value)";
$this->db->query($sql);
}
}
}
/* components */
elseif ($component)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, but delete and insert again
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($objwatcher->$fieldname as $comp)
{
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($id, $values)";
$this->db->query($sql);
}
}
}
/* check the one-to-many relation */
elseif ($o2m)
{
if (! $this->areSameEntityCollection($objcache->$fieldname, $objwatcher->$fieldname) )
{
$manyClassName = (string)$o2m['class'];
$manyClassNode = $this->getRootClassNode($manyClassName);
$manyClassRootName = (string)$manyClassNode['name'];
$tablename = (string)$manyClassNode['table'] == '' ? (string)$manyClassNode['name'] : (string)$manyClassNode['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$manyClassNode->id['name'];
$pkname = (string)$manyClassNode->id['column'] == '' ? $pkfield : (string)$manyClassNode->id['column'];
$pkname = $this->db->sqlquote($pkname);
// update all
$ids = array();
foreach ($objwatcher->$fieldname as $mObj)
{
$watcherId = $this->getObjId($mObj);
// the many side object is new
if ($watcherId === null)
{
// cascade save
if ( strpos( (string)$set['cascade'], 'save-update') !== false )
$watcherId = $this->save($mObj);
else
continue;
}
else
{
// deattached object, then make it attached
if (! $this->inCache($manyClassRootName, $watcherId, true) )
{
$this->update($mObj, $watcherId);
// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
// so we should do the dirty check at once
$this->doDirtyCheck($this->objcache[$manyClassRootName][$watcherId], $mObj);
}
}
if (is_string($watcherId))
$watcherId = "'$watcherId'";
$ids[] = $watcherId;
}
$ids = implode(',', $ids);
$sql = "UPDATE $tablename SET $keyname=$id WHERE $pkname in ($ids)";
$this->db->query($sql);
//TODO: the old unlinked entity will not be deleted? (It's a work of user)
}
}
/* check the many-to-many relation */
elseif ($m2m)
{
if (! $this->areSameEntityCollection($objcache->$fieldname, $objwatcher->$fieldname) )
{
$manyClassName = (string)$m2m['class'];
$manyClassNode = $this->getRootClassNode($manyClassName);
$manyClassRootName = (string)$manyClassNode['name'];
$tablename = (string)$set['table'];
$tablename = $this->db->sqlquote($tablename);
$pkfield = (string)$manyClassNode->id['name'];
$pkname = (string)$m2m['column'];
$pkname = $this->db->sqlquote($pkname);
// delete and then insert
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
foreach ($objwatcher->$fieldname as $mObj)
{
$watcherId = $this->getObjId($mObj);
// the many side object is new
if ($watcherId === null)
{
// cascade save
if ( strpos( (string)$set['cascade'], 'save-update') !== false )
$watcherId = $this->save($mObj);
else
continue;
}
if (is_string($watcherId))
$watcherId = "'$watcherId'";
$sql = "INSERT INTO $tablename ($keyname, $pkname) VALUES($id, $watcherId)";
$this->db->query($sql);
// deattached object, then make it attached
if (! $this->inCache($manyClassRootName, $watcherId, true) )
{
$this->update($mObj, $watcherId);
// the new attached object changes the cache, but the foreach in Session.dirtycheck() will not check it at all!
// so we should do the dirty check at once
$this->doDirtyCheck($this->objcache[$manyClassRootName][$watcherId], $mObj);
}
}
}
}
}
// bag
foreach ($classNode->bag as $bag)
{
$fieldname = (string)$bag['name'];
// set table name same to the fieldname by default
$tablename = (string)$bag['table'] == '' ? $fieldname : (string)$bag['table'];
$tablename = $this->db->sqlquote($tablename);
//$pkname = (string)$bag->id['column'];
$keyname = (string)$bag->key['column'];
$keyname = $this->db->sqlquote($keyname);
$elementname = (string)$bag->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $bag->component;
/* elements */
if ($elementname)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, because no primary key information, just delete and insert again.
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
foreach ($objwatcher->$fieldname as $value)
{
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($keyname, $elementname) VALUES ($id, $value)";
$this->db->query($sql);
}
}
}
/* components */
elseif ($component)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, but delete and insert again
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($objwatcher->$fieldname as $comp)
{
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($keyname, $columns) VALUES ($id, $values)";
$this->db->query($sql);
}
}
}
}
// map, the key of the map must be type string
foreach ($classNode->map as $map)
{
$fieldname = (string)$map['name'];
// set table name same to the fieldname by default
$tablename = (string)$map['table'] == '' ? $fieldname : (string)$map['table'];
$tablename = $this->db->sqlquote($tablename);
$keyname = (string)$map->key['column'];
$keyname = $this->db->sqlquote($keyname);
$mapkeyname = (string)$map->{'map-key'}['column'];
$mapkeyname = $this->db->sqlquote($mapkeyname);
$elementname = (string)$map->element['column'];
$elementname = $this->db->sqlquote($elementname);
$component = $map->component;
/* elements */
if ($elementname)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, because no primary key information, just delete and insert again.
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
foreach ($objwatcher->$fieldname as $key=>$value)
{
if (is_string($value))
$value = "'$value'";
$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $elementname) VALUES ($id, '$key', $value)";
$this->db->query($sql);
}
}
}
/* components */
elseif ($component)
{
// array compare (not matter the order)
if ($objwatcher->$fieldname != $objcache->$fieldname)
{
// wont update, but delete and insert again
$sql = "DELETE FROM $tablename WHERE $keyname=$id";
$this->db->query($sql);
$compFieldnames = array();
$columns = array();
$this->parseComponentCollection($component, $compFieldnames, $columns);
$columns = implode(',', $columns);
// read the value from object and save them into database
foreach ($objwatcher->$fieldname as $key=>$comp)
{
$values = array();
foreach ($compFieldnames as $compFieldname)
{
$value = $comp->$compFieldname;
if (is_string($value))
$value = "'$value'";
$values[] = $value;
}
$values = implode(',', $values);
$sql = "INSERT INTO $tablename ($keyname, $mapkeyname, $columns) VALUES ($id, '$key', $values)";
$this->db->query($sql);
}
}
}
}
}
}