Author: dr
Date: Wed Aug 1 15:44:09 2007
New Revision: 5798
Log:
- Implemented deleting data in the stores, so that no stale data exists.
- Fixed issues with retrieving/storing data.
- Allows access (through a read only property) to a tree node's associated
tree.
- Optimized deleting nodes that have children by fetching the subtree first
and then running a query to delete the nodes in one go.
Modified:
trunk/Tree/src/backends/db_parent_child.php
trunk/Tree/src/backends/memory.php
trunk/Tree/src/backends/xml.php
trunk/Tree/src/interfaces/data_store.php
trunk/Tree/src/stores/db_external.php
trunk/Tree/src/stores/memory.php
trunk/Tree/src/stores/xml_internal.php
trunk/Tree/src/tree_node.php
trunk/Tree/tests/db_parent_child_tree.php
trunk/Tree/tests/db_tree.php
trunk/Tree/tests/files/init.xml
trunk/Tree/tests/tree.php
trunk/Tree/tests/tree_node.php
trunk/Tree/tests/xml_tree.php
Modified: trunk/Tree/src/backends/db_parent_child.php
==============================================================================
--- trunk/Tree/src/backends/db_parent_child.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/db_parent_child.php [iso-8859-1] Wed Aug 1
15:44:09 2007
@@ -345,6 +345,7 @@
$q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) );
$s = $q->prepare();
$s->execute();
+ $this->store->deleteDataForAllNodes();
$q = $db->createInsertQuery();
$q->insertInto( $db->quoteIdentifier( $this->indexTableName ) )
@@ -382,18 +383,15 @@
$this->store->storeDataForNode( $childNode, $childNode->data );
}
- private function deleteChildNodes( $nodeId )
+ private function deleteNodes( ezcTreeNodeList $list )
{
$db = $this->dbh;
$q = $db->createDeleteQuery();
-
- foreach ( $this->fetchChildRecords( $nodeId ) as $record )
- {
- $this->deleteChildNodes( $record['id'] );
- }
-
- $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) )
- ->where( $q->expr->eq( 'id', $q->bindValue( $nodeId ) ) );
+ $q->deleteFrom( $db->quoteIdentifier( $this->indexTableName ) );
+
+ $idList = array_keys( $list->getNodes() );
+
+ $q->where( $q->expr->in( 'id', $idList ) );
$s = $q->prepare();
$s->execute();
}
@@ -411,7 +409,9 @@
return;
}
- $this->deleteChildNodes( $id );
+ $nodeList = $this->fetchSubtree( $id );
+ $this->deleteNodes( $nodeList );
+ $this->store->deleteDataForNodes( $nodeList );
}
/**
Modified: trunk/Tree/src/backends/memory.php
==============================================================================
--- trunk/Tree/src/backends/memory.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/memory.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -347,8 +347,9 @@
*/
public function setRootNode( ezcTreeNode $node )
{
- // wipe nodelist
+ // wipe nodelist and data
$this->nodeList = array();
+ $this->store->deleteDataForAllNodes();
// replace root node
$newObj = new ezcTreeMemoryNode( $node, array() );
@@ -401,8 +402,9 @@
// locate node to move
$nodeToDelete = $this->getNodeById( $id );
- // fetch the whole subtree
+ // fetch the whole subtree and delete all the associated data
$children = $nodeToDelete->node->fetchSubtree();
+ $this->store->deleteDataForNodes( $children );
// Use the parent to remove the child
unset( $nodeToDelete->parent->children[$id] );
Modified: trunk/Tree/src/backends/xml.php
==============================================================================
--- trunk/Tree/src/backends/xml.php [iso-8859-1] (original)
+++ trunk/Tree/src/backends/xml.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -112,7 +112,7 @@
/**
* Saves the internal DOM representation of the tree back to disk
*/
- private function saveFile()
+ public function saveFile()
{
$this->dom->save( $this->xmlFile );
}
@@ -446,6 +446,7 @@
$this->delete( $id );
}
}
+ $this->store->deleteDataForAllNodes();
// Create new root node
$root = $this->dom->createElement( 'node' );
@@ -501,20 +502,23 @@
return;
}
+ // Delete all the associated data
+ $nodeList = $this->fetchSubtree( $id );
+ $this->store->deleteDataForNodes( $nodeList );
+
// locate node to move
$nodeToDelete = $this->getNodeById( $id );
- // Use the parent to remove the child
+ // Remove the ID on all children by hand as this would crash in PHP <=
5.2.3
$nodeToDelete->removeAttribute( "id" );
- // Remove the ID on all children by hand as this would crash in PHP <=
5.2.3
$children = $nodeToDelete->getElementsByTagName( 'node' );
foreach ( $children as $child )
{
$child->removeAttribute( "id" );
}
- // Remove the node itself
+ // Use the parent to remove the child
$nodeToDelete->parentNode->removeChild( $nodeToDelete );
if ( !$this->inTransactionCommit )
Modified: trunk/Tree/src/interfaces/data_store.php
==============================================================================
--- trunk/Tree/src/interfaces/data_store.php [iso-8859-1] (original)
+++ trunk/Tree/src/interfaces/data_store.php [iso-8859-1] Wed Aug 1 15:44:09
2007
@@ -16,6 +16,25 @@
*/
interface ezcTreeDataStore
{
+ /**
+ * Deletes the data for the node $node from the data store.
+ *
+ * @param ezcTreeNode $node
+ */
+ public function deleteDataForNode( ezcTreeNode $node );
+
+ /**
+ * Deletes the data for all the nodes in the node list $nodeList.
+ *
+ * @param ezcTreeNodeList $nodeList
+ */
+ public function deleteDataForNodes( ezcTreeNodeList $nodeList );
+
+ /**
+ * Deletes the data for all the nodes in the store.
+ */
+ public function deleteDataForAllNodes();
+
/**
* Retrieves the data for the node $node from the data store and assigns it
* to the node's 'data' property.
Modified: trunk/Tree/src/stores/db_external.php
==============================================================================
--- trunk/Tree/src/stores/db_external.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/db_external.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -71,6 +71,45 @@
}
/**
+ * Deletes the data for the node $node from the data store.
+ *
+ * @param ezcTreeNode $node
+ */
+ public function deleteDataForNode( ezcTreeNode $node )
+ {
+ }
+
+ /**
+ * Deletes the data for all the nodes in the node list $nodeList.
+ *
+ * @param ezcTreeNodeList $nodeList
+ */
+ public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+ {
+ $nodeIdsToDelete = array_keys( $nodeList->getNodes() );
+
+ $db = $this->dbHandler;
+ $q = $db->createDeleteQuery();
+ $q->deleteFrom( $db->quoteIdentifier( $this->table ) )
+ ->where( $q->expr->in( $db->quoteIdentifier( $this->idField ),
$nodeIdsToDelete ) );
+ $s = $q->prepare();
+ $s->execute();
+ }
+
+ /**
+ * Deletes the data for all the nodes in the store.
+ */
+ public function deleteDataForAllNodes()
+ {
+ $db = $this->dbHandler;
+ $q = $db->createDeleteQuery();
+
+ $q->deleteFrom( $db->quoteIdentifier( $this->table ) );
+ $s = $q->prepare();
+ $s->execute();
+ }
+
+ /**
* Takes the data from the executed query and uses the $dataField
* property to filter out the wanted data for this node.
*
@@ -110,6 +149,7 @@
throw new ezcTreeDataStoreMissingDataException( $node->id );
}
$node->data = $this->filterDataFromResult( $result );
+ $node->dataFetched = true;
}
/**
@@ -142,6 +182,7 @@
foreach ( $s as $result )
{
$nodeList[$result['id']]->data = $this->filterDataFromResult(
$result );
+ $nodeList[$result['id']]->dataFetched = true;
}
}
@@ -191,6 +232,7 @@
}
$s = $q->prepare();
$s->execute();
+ $node->dataStored = true;
}
}
?>
Modified: trunk/Tree/src/stores/memory.php
==============================================================================
--- trunk/Tree/src/stores/memory.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/memory.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -16,6 +16,34 @@
*/
class ezcTreeMemoryDataStore implements ezcTreeDataStore
{
+ /**
+ * Deletes the data for the node $node from the data store.
+ *
+ * @param ezcTreeNode $node
+ */
+ public function deleteDataForNode( ezcTreeNode $node )
+ {
+ /* This is a no-op as the data is part of the node */
+ }
+
+ /**
+ * Deletes the data for all the nodes in the node list $nodeList.
+ *
+ * @param ezcTreeNodeList $nodeList
+ */
+ public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+ {
+ /* This is a no-op as the data is part of the node */
+ }
+
+ /**
+ * Deletes the data for all the nodes in the store.
+ */
+ public function deleteDataForAllNodes()
+ {
+ /* This is a no-op as the data is part of the node */
+ }
+
/**
* Retrieves the data for the node $node from the data store and assigns it
* to the node's 'data' property.
Modified: trunk/Tree/src/stores/xml_internal.php
==============================================================================
--- trunk/Tree/src/stores/xml_internal.php [iso-8859-1] (original)
+++ trunk/Tree/src/stores/xml_internal.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -18,6 +18,33 @@
class ezcTreeXmlInternalDataStore extends ezcTreeXmlDataStore
{
/**
+ * Deletes the data for the node $node from the data store.
+ *
+ * @param ezcTreeNode $node
+ */
+ public function deleteDataForNode( ezcTreeNode $node )
+ {
+ }
+
+ /**
+ * Deletes the data for all the nodes in the node list $nodeList.
+ *
+ * @param ezcTreeNodeList $nodeList
+ */
+ public function deleteDataForNodes( ezcTreeNodeList $nodeList )
+ {
+ /* This is a no-op as the data is part of the node */
+ }
+
+ /**
+ * Deletes the data for all the nodes in the store.
+ */
+ public function deleteDataForAllNodes()
+ {
+ /* This is a no-op as the data is part of the node */
+ }
+
+ /**
* Retrieves the data for the node $node from the data store and assigns it
* to the node's 'data' property.
*
@@ -27,7 +54,13 @@
{
$id = $node->id;
$elem = $this->dom->getElementById( "id$id" );
- $node->data = $elem->getElementsByTagNameNS(
'http://components.ez.no/Tree/data', 'data' )->item(0)->firstChild->data;
+ $dataElem = $elem->getElementsByTagNameNS(
'http://components.ez.no/Tree/data', 'data' )->item(0);
+ if ( $dataElem === null )
+ {
+ throw new ezcTreeDataStoreMissingDataException( $node->id );
+ }
+ $node->data = $dataElem->firstChild->data;
+ $node->dataFetched = true;
}
/**
@@ -54,10 +87,22 @@
*/
public function storeDataForNode( ezcTreeNode $node )
{
+ // Locate the element
$id = $node->id;
$elem = $this->dom->getElementById( "id$id" );
- $dataNode = $elem->ownerDocument->createElement( 'etd:data',
$node->data );
+
+ // Locate the data element, and remove it
+ $dataElem = $elem->getElementsByTagNameNS(
'http://components.ez.no/Tree/data', 'data' )->item(0);
+ if ( $dataElem !== null )
+ {
+ $dataElem->parentNode->removeChild( $dataElem );
+ }
+
+ // Create the new data element and add it
+ $dataNode = $elem->ownerDocument->createElementNS(
'http://components.ez.no/Tree/data', 'etd:data', $node->data );
$elem->appendChild( $dataNode );
+ $node->dataStored = true;
+ $node->tree->saveFile();
}
}
?>
Modified: trunk/Tree/src/tree_node.php
==============================================================================
--- trunk/Tree/src/tree_node.php [iso-8859-1] (original)
+++ trunk/Tree/src/tree_node.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -14,10 +14,15 @@
* isSiblingOf) are all marshalled to calls on the tree (that is stored in the
* $tree private variable) itself.
*
- * @property-read string $id The ID that uniquely identifies a node
- * @property mixed $data The data belonging to a node
- * @property-read bool $dataFetched Whether the data for this node has been
- * fetched.
+ * @property-read string $id The ID that uniquely identifies a node
+ * @property-read ezcTree $tree The tree object that this node belongs
to
+ * @property mixed $data The data belonging to a node
+ * @property bool $dataFetched Whether the data for this node has been
+ * fetched. Should *only* be modified by
+ * data store implementations.
+ * @property bool $dataStored Whether the data for this node has been
+ * stored. Should *only* be modified by
+ * data store implementations.
*
* @package Tree
* @version //autogentag//
@@ -33,13 +38,6 @@
private $properties = array();
/**
- * Holds a link to the tree that this node is part of
- *
- * @var ezcTree
- */
- private $tree;
-
- /**
* Constructs a new ezcTreeNode object with ID $id on tree $tree
*
* @param ezcTree $tree
@@ -48,13 +46,14 @@
*/
public function __construct( ezcTree $tree, $id )
{
- $this->tree = $tree;
$this->properties['id'] = (string) $id;
+ $this->properties['tree'] = $tree;
if ( func_num_args() === 2 )
{
$this->properties['data'] = null;
$this->properties['dataFetched'] = false;
+ $this->properties['dataStored'] = true;
if ( $tree->prefetch )
{
@@ -65,6 +64,7 @@
{
$this->properties['data'] = func_get_arg( 2 );
$this->properties['dataFetched'] = true;
+ $this->properties['dataStored'] = false;
}
}
@@ -89,6 +89,8 @@
// break intentionally missing
case 'id':
case 'dataFetched':
+ case 'dataStored':
+ case 'tree':
return $this->properties[$name];
}
@@ -110,14 +112,28 @@
switch ( $name )
{
case 'id':
- case 'dataFetched':
+ case 'tree':
throw new ezcBasePropertyPermissionException( $name,
ezcBasePropertyPermissionException::READ );
case 'data':
$this->properties[$name] = $value;
+ $this->properties['dataStored'] = false;
+ if ( $this->properties['dataFetched'] )
+ {
+ $this->tree->store->storeDataForNode( $this );
+ }
$this->properties['dataFetched'] = true;
return;
+ case 'dataFetched':
+ case 'dataStored':
+ if ( !is_bool( $value ) )
+ {
+ throw new ezcBaseValueException( $name, $value, 'boolean'
);
+ }
+ $this->properties[$name] = $value;
+ break;
+
default:
throw new ezcBasePropertyNotFoundException( $name );
}
Modified: trunk/Tree/tests/db_parent_child_tree.php
==============================================================================
--- trunk/Tree/tests/db_parent_child_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/db_parent_child_tree.php [iso-8859-1] Wed Aug 1 15:44:09
2007
@@ -184,8 +184,53 @@
}
}
+ public function testStoreUpdatedData()
+ {
+ $this->emptyTables();
+
+ $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data',
'id', 'data' );
+ $tree = ezcTreeDbParentChild::create(
+ $this->dbh,
+ 'parent_child',
+ $store
+ );
+
+ $root = $tree->createNode( 1, "Camelinae" );
+ $tree->setRootNode( $root );
+
+ $root->addChild( $tree->createNode( 2, "Lama" ) );
+ $root->addChild( $tree->createNode( 3, "Vicugna" ) );
+ $root->addChild( $tree->createNode( 4, "Camelus" ) );
+
+ // start over
+ $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data',
'id', 'data' );
+ $tree = new ezcTreeDbParentChild(
+ $this->dbh,
+ 'parent_child',
+ $store
+ );
+
+ $camelus = $tree->fetchNodeById( 4 );
+ self::assertSame( "Camelus", $camelus->data );
+ $camelus->data = "Something Wrong";
+ $camelus->data = "Camels";
+
+ // start over
+ $store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data',
'id', 'data' );
+ $tree = new ezcTreeDbParentChild(
+ $this->dbh,
+ 'parent_child',
+ $store
+ );
+
+ $camelus = $tree->fetchNodeById( 4 );
+ self::assertSame( "Camels", $camelus->data );
+ }
+
public function testCreateDbTreeWithTransaction()
{
+ $this->emptyTables();
+
$store = new ezcTreeDbExternalTableDataStore( $this->dbh, 'data',
'id', 'data' );
$tree = ezcTreeDbParentChild::create(
$this->dbh,
Modified: trunk/Tree/tests/db_tree.php
==============================================================================
--- trunk/Tree/tests/db_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/db_tree.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -51,7 +51,7 @@
}
// add data
- for ( $i = 1; $i <= 9; $i++ )
+ for ( $i = 1; $i <= 8; $i++ )
{
$this->dbh->exec( "INSERT INTO data(id, data) values ( $i,
'Node $i' )" );
}
Modified: trunk/Tree/tests/files/init.xml
==============================================================================
--- trunk/Tree/tests/files/init.xml [iso-8859-1] (original)
+++ trunk/Tree/tests/files/init.xml [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -23,9 +23,7 @@
</node>
<node id="id5">
<etd:data>Node 5</etd:data>
- <node id="id9">
- <etd:data>Node 9</etd:data>
- </node>
+ <node id="id9"/>
</node>
</node>
</tree>
Modified: trunk/Tree/tests/tree.php
==============================================================================
--- trunk/Tree/tests/tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/tree.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -556,6 +556,19 @@
self::assertSame( 8, $tree->getChildCountRecursive( '1' ) );
}
+ public function testTreeDeleteNodeAndAddNew()
+ {
+ $tree = $this->setUpTestTree();
+
+ $tree->delete( '5' );
+ self::assertSame( false, $tree->nodeExists( '5' ) );
+ self::assertSame( false, $tree->nodeExists( '9' ) );
+
+ $node1 = $tree->fetchNodeById( 1 );
+ $cervus = $tree->createNode( 9, 'Cervus' );
+ $node1->addChild( $cervus );
+ }
+
public function testTreeTransactionDoubleStart()
{
$tree = $this->setUpTestTree();
@@ -672,6 +685,26 @@
}
self::assertSame( true, $tree->nodeExists( '42' ) );
}
+
+ public function testMissingDataException()
+ {
+ // not applicable from memory store as that always has data
+ if ( get_class( $this ) !== 'ezcTreeMemoryTest' )
+ {
+ $tree = $this->setUpTestTree();
+
+ $node = $tree->fetchNodeById( 9 );
+ try
+ {
+ $data = $node->data;
+ self::fail( "Expected exception not thrown." );
+ }
+ catch ( ezcTreeDataStoreMissingDataException $e )
+ {
+ self::assertSame( "The data store does not have data stored
for the node with ID '9'.", $e->getMessage() );
+ }
+ }
+ }
}
?>
Modified: trunk/Tree/tests/tree_node.php
==============================================================================
--- trunk/Tree/tests/tree_node.php [iso-8859-1] (original)
+++ trunk/Tree/tests/tree_node.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -140,6 +140,56 @@
self::assertSame( true, $node->dataFetched );
}
+ public function testSetDataFetchedNotBool()
+ {
+ $node = new ezcTreeNode( $this->tree, 'Si', 'Silicon' );
+
+ try
+ {
+ $node->dataFetched = 42;
+ self::fail( "Expected exception not thrown" );
+ }
+ catch ( ezcBaseValueException $e )
+ {
+ self::assertSame( "The value '42' that you were trying to assign
to setting 'dataFetched' is invalid. Allowed values are: boolean.",
$e->getMessage() );
+ }
+ }
+
+ public function testSetDataStoredNotBool()
+ {
+ $node = new ezcTreeNode( $this->tree, 'P', 'Phosphorus' );
+
+ try
+ {
+ $node->dataStored = "oops!";
+ self::fail( "Expected exception not thrown" );
+ }
+ catch ( ezcBaseValueException $e )
+ {
+ self::assertSame( "The value 'oops!' that you were trying to
assign to setting 'dataStored' is invalid. Allowed values are: boolean.",
$e->getMessage() );
+ }
+ }
+
+ public function testSetDataFetched()
+ {
+ $node = new ezcTreeNode( $this->tree, 'S', 'Sulfur' );
+
+ $node->dataFetched = true;
+ self::assertSame( true, $node->dataFetched );
+ $node->dataFetched = false;
+ self::assertSame( false, $node->dataFetched );
+ }
+
+ public function testSetDataStored()
+ {
+ $node = new ezcTreeNode( $this->tree, 'Cl', 'Chlorine' );
+
+ $node->dataStored = true;
+ self::assertSame( true, $node->dataStored );
+ $node->dataStored = false;
+ self::assertSame( false, $node->dataStored );
+ }
+
public static function suite()
{
return new PHPUnit_Framework_TestSuite( "ezcTreeNodeTest" );
Modified: trunk/Tree/tests/xml_tree.php
==============================================================================
--- trunk/Tree/tests/xml_tree.php [iso-8859-1] (original)
+++ trunk/Tree/tests/xml_tree.php [iso-8859-1] Wed Aug 1 15:44:09 2007
@@ -95,6 +95,41 @@
);
}
+ public function testStoreUpdatedData()
+ {
+ $tree = ezcTreeXml::create(
+ $this->tempDir . '/new-tree.xml',
+ new ezcTreeXmlInternalDataStore()
+ );
+
+ $root = $tree->createNode( 1, "Camelinae" );
+ $tree->setRootNode( $root );
+
+ $root->addChild( $tree->createNode( 2, "Lama" ) );
+ $root->addChild( $tree->createNode( 3, "Vicugna" ) );
+ $root->addChild( $tree->createNode( 4, "Camelus" ) );
+
+ // start over
+ $tree = new ezcTreeXml(
+ $this->tempDir . '/new-tree.xml',
+ new ezcTreeXmlInternalDataStore()
+ );
+
+ $camelus = $tree->fetchNodeById( 4 );
+ self::assertSame( "Camelus", $camelus->data );
+ $camelus->data = "Something Wrong";
+ $camelus->data = "Camels";
+
+ // start over
+ $tree = new ezcTreeXml(
+ $this->tempDir . '/new-tree.xml',
+ new ezcTreeXmlInternalDataStore()
+ );
+
+ $camelus = $tree->fetchNodeById( 4 );
+ self::assertSame( "Camels", $camelus->data );
+ }
+
public static function suite()
{
return new PHPUnit_Framework_TestSuite( "ezcTreeXmlTest" );
--
svn-components mailing list
[email protected]
http://lists.ez.no/mailman/listinfo/svn-components