From 2807a83d5d5f6d18bdd134b0e097ee472f43e6ca Mon Sep 17 00:00:00 2001 From: romanb Date: Mon, 27 Jul 2009 09:50:22 +0000 Subject: [PATCH] [2.0] Started to simplify commit order calculation. --- .../ORM/Internal/CommitOrderCalculator.php | 157 ++++++++---------- lib/Doctrine/ORM/Internal/CommitOrderNode.php | 144 ---------------- lib/Doctrine/ORM/UnitOfWork.php | 25 +-- .../Tests/ORM/CommitOrderCalculatorTest.php | 44 ++--- 4 files changed, 104 insertions(+), 266 deletions(-) delete mode 100644 lib/Doctrine/ORM/Internal/CommitOrderNode.php diff --git a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php index 06d1a4f10..6f12cc871 100644 --- a/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php +++ b/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php @@ -30,88 +30,14 @@ namespace Doctrine\ORM\Internal; */ class CommitOrderCalculator { - private $_currentTime; + const NOT_VISITED = 1; + const IN_PROGRESS = 2; + const VISITED = 3; - /** - * The node list used for sorting. - * - * @var array - */ - private $_nodes = array(); - - /** - * The topologically sorted list of items. Note that these are not nodes - * but the wrapped items. - * - * @var array - */ - private $_sorted; - - /** - * Orders the given list of CommitOrderNodes based on their dependencies. - * - * Uses a depth-first search (DFS) to traverse the graph. - * The desired topological sorting is the reverse postorder of these searches. - * - * @param array $nodes The list of (unordered) CommitOrderNodes. - * @return array The list of ordered items. These are the items wrapped in the nodes. - */ - public function getCommitOrder() - { - // Check whether we need to do anything. 0 or 1 node is easy. - $nodeCount = count($this->_nodes); - if ($nodeCount == 0) { - return array(); - } else if ($nodeCount == 1) { - $node = array_pop($this->_nodes); - return array($node->getClass()); - } - - $this->_sorted = array(); - - // Init - foreach ($this->_nodes as $node) { - $node->markNotVisited(); - $node->setPredecessor(null); - } - - $this->_currentTime = 0; - - // Go - foreach ($this->_nodes as $node) { - if ($node->isNotVisited()) { - $node->visit(); - } - } - - return $this->_sorted; - } - - /** - * Adds a node to consider when ordering. - * - * @param mixed $key Somme arbitrary key for the node (must be unique!). - * @param unknown_type $node - */ - public function addNode($key, $node) - { - $this->_nodes[$key] = $node; - } - - public function addNodeWithItem($key, $item) - { - $this->_nodes[$key] = new CommitOrderNode($item, $this); - } - - public function getNodeForKey($key) - { - return $this->_nodes[$key]; - } - - public function hasNodeWithKey($key) - { - return isset($this->_nodes[$key]); - } + private $_nodeStates = array(); + private $_classes = array(); // The nodes to sort + private $_relatedClasses = array(); + private $_sorted = array(); /** * Clears the current graph and the last result. @@ -124,13 +50,74 @@ class CommitOrderCalculator $this->_sorted = array(); } - public function getNextTime() + /** + * Gets a valid commit order for all current nodes. + * + * Uses a depth-first search (DFS) to traverse the graph. + * The desired topological sorting is the reverse postorder of these searches. + * + * @return array The list of ordered items. These are the items wrapped in the nodes. + */ + public function getCommitOrder() { - return ++$this->_currentTime; + // Check whether we need to do anything. 0 or 1 node is easy. + $nodeCount = count($this->_classes); + if ($nodeCount == 0) { + return array(); + } else if ($nodeCount == 1) { + return $this->_classes; + } + + // Init + $this->_sorted = array(); + $this->_nodeStates = array(); + foreach ($this->_classes as $node) { + $this->_nodeStates[$node->name] = self::NOT_VISITED; + } + + // Go + foreach ($this->_classes as $node) { + if ($this->_nodeStates[$node->name] == self::NOT_VISITED) { + $this->_visitNode($node); + } + } + + return array_reverse($this->_sorted); } - public function prependNode($node) + private function _visitNode($node) { - array_unshift($this->_sorted, $node->getClass()); + $this->_nodeStates[$node->name] = self::IN_PROGRESS; + + if (isset($this->_relatedClasses[$node->name])) { + foreach ($this->_relatedClasses[$node->name] as $relatedNode) { + if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) { + $this->_visitNode($relatedNode); + } + if ($this->_nodeStates[$relatedNode->name] == self::IN_PROGRESS) { + // back edge => cycle + //TODO: anything to do here? + } + } + } + + $this->_nodeStates[$node->name] = self::VISITED; + + $this->_sorted[] = $node; + } + + public function addDependency($fromClass, $toClass) + { + $this->_relatedClasses[$fromClass->name][] = $toClass; + } + + public function hasClass($className) + { + return isset($this->_classes[$className]); + } + + public function addClass($class) + { + $this->_classes[$class->name] = $class; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Internal/CommitOrderNode.php b/lib/Doctrine/ORM/Internal/CommitOrderNode.php deleted file mode 100644 index 28aaf15c8..000000000 --- a/lib/Doctrine/ORM/Internal/CommitOrderNode.php +++ /dev/null @@ -1,144 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Internal; - -/** - * A CommitOrderNode is a temporary wrapper around ClassMetadata instances - * that is used to sort the order of commits in a UnitOfWork. - * - * @since 2.0 - * @author Roman Borschel - */ -class CommitOrderNode -{ - const NOT_VISITED = 1; - const IN_PROGRESS = 2; - const VISITED = 3; - - private $_traversalState; - private $_predecessor; - private $_status; - private $_calculator; - private $_relatedNodes = array(); - - /* The "time" when this node was first discovered during traversal */ - public $discoveryTime; - /* The "time" when this node was finished during traversal */ - public $finishingTime; - - /* The wrapped object */ - private $_wrappedObj; - - /** - * Constructor. - * Creates a new node. - * - * @param mixed $wrappedObj The object to wrap. - * @param Doctrine\ORM\Internal\CommitOrderCalculator $calc The calculator. - */ - public function __construct($wrappedObj, CommitOrderCalculator $calc) - { - $this->_wrappedObj = $wrappedObj; - $this->_calculator = $calc; - } - - /** - * Gets the wrapped object. - * - * @return mixed - */ - public function getClass() - { - return $this->_wrappedObj; - } - - public function setPredecessor($node) - { - $this->_predecessor = $node; - } - - public function getPredecessor() - { - return $this->_predecessor; - } - - public function markNotVisited() - { - $this->_traversalState = self::NOT_VISITED; - } - - public function markInProgress() - { - $this->_traversalState = self::IN_PROGRESS; - } - - public function markVisited() - { - $this->_traversalState = self::VISITED; - } - - public function isNotVisited() - { - return $this->_traversalState == self::NOT_VISITED; - } - - public function isInProgress() - { - return $this->_traversalState == self::IN_PROGRESS; - } - - public function visit() - { - $this->markInProgress(); - $this->discoveryTime = $this->_calculator->getNextTime(); - - foreach ($this->getRelatedNodes() as $node) { - if ($node->isNotVisited()) { - $node->setPredecessor($this); - $node->visit(); - } - if ($node->isInProgress()) { - // back edge => cycle - //TODO: anything to do here? - } - } - - $this->markVisited(); - $this->_calculator->prependNode($this); - $this->finishingTime = $this->_calculator->getNextTime(); - } - - public function getRelatedNodes() - { - return $this->_relatedNodes; - } - - /** - * Adds a directed dependency (an edge on the graph). "$this -before-> $other". - * - * @param Doctrine\ORM\Internal\CommitOrderNode $node - */ - public function before(CommitOrderNode $node) - { - $this->_relatedNodes[] = $node; - } -} \ No newline at end of file diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 23951f7f1..aadb457c1 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -798,33 +798,24 @@ class UnitOfWork implements PropertyChangedListener $newNodes = array(); foreach ($entityChangeSet as $entity) { $className = get_class($entity); - if ( ! $this->_commitOrderCalculator->hasNodeWithKey($className)) { - $this->_commitOrderCalculator->addNodeWithItem( - $className, // index/key - $this->_em->getClassMetadata($className) // item - ); - $newNodes[] = $this->_commitOrderCalculator->getNodeForKey($className); + if ( ! $this->_commitOrderCalculator->hasClass($className)) { + $class = $this->_em->getClassMetadata($className); + $this->_commitOrderCalculator->addClass($class); + $newNodes[] = $class; } } // Calculate dependencies for new nodes - foreach ($newNodes as $node) { - $class = $node->getClass(); + foreach ($newNodes as $class) { foreach ($class->associationMappings as $assocMapping) { //TODO: should skip target classes that are not in the changeset. if ($assocMapping->isOwningSide) { $targetClass = $this->_em->getClassMetadata($assocMapping->targetEntityName); - $targetClassName = $targetClass->name; - // If the target class does not yet have a node, create it - if ( ! $this->_commitOrderCalculator->hasNodeWithKey($targetClassName)) { - $this->_commitOrderCalculator->addNodeWithItem( - $targetClassName, // index/key - $targetClass // item - ); + if ( ! $this->_commitOrderCalculator->hasClass($targetClass->name)) { + $this->_commitOrderCalculator->addClass($targetClass); } // add dependency - $otherNode = $this->_commitOrderCalculator->getNodeForKey($targetClassName); - $otherNode->before($node); + $this->_commitOrderCalculator->addDependency($targetClass, $class); } } } diff --git a/tests/Doctrine/Tests/ORM/CommitOrderCalculatorTest.php b/tests/Doctrine/Tests/ORM/CommitOrderCalculatorTest.php index 5593fc889..d51aa344a 100644 --- a/tests/Doctrine/Tests/ORM/CommitOrderCalculatorTest.php +++ b/tests/Doctrine/Tests/ORM/CommitOrderCalculatorTest.php @@ -2,6 +2,8 @@ namespace Doctrine\Tests\ORM; +use Doctrine\ORM\Mapping\ClassMetadata; + require_once __DIR__ . '/../TestInit.php'; /** @@ -19,34 +21,36 @@ class CommitOrderCalculatorTest extends \Doctrine\Tests\OrmTestCase { $this->_calc = new \Doctrine\ORM\Internal\CommitOrderCalculator(); } - - /** Helper to create an array of nodes */ - private function _createNodes(array $names) - { - $nodes = array(); - foreach ($names as $name) { - $node = new \Doctrine\ORM\Internal\CommitOrderNode($name, $this->_calc); - $nodes[$name] = $node; - $this->_calc->addNode($node->getClass(), $node); - } - return $nodes; - } public function testCommitOrdering1() { - $nodes = $this->_createNodes(array("node1", "node2", "node3", "node4", "node5")); + $class1 = new ClassMetadata(__NAMESPACE__ . '\NodeClass1'); + $class2 = new ClassMetadata(__NAMESPACE__ . '\NodeClass2'); + $class3 = new ClassMetadata(__NAMESPACE__ . '\NodeClass3'); + $class4 = new ClassMetadata(__NAMESPACE__ . '\NodeClass4'); + $class5 = new ClassMetadata(__NAMESPACE__ . '\NodeClass5'); - $nodes['node1']->before($nodes['node2']); - $nodes['node2']->before($nodes['node3']); - $nodes['node3']->before($nodes['node4']); - $nodes['node5']->before($nodes['node1']); + $this->_calc->addClass($class1); + $this->_calc->addClass($class2); + $this->_calc->addClass($class3); + $this->_calc->addClass($class4); + $this->_calc->addClass($class5); - shuffle($nodes); // some randomness + $this->_calc->addDependency($class1, $class2); + $this->_calc->addDependency($class2, $class3); + $this->_calc->addDependency($class3, $class4); + $this->_calc->addDependency($class5, $class1); $sorted = $this->_calc->getCommitOrder(); // There is only 1 valid ordering for this constellation - $correctOrder = array("node5", "node1", "node2", "node3", "node4"); + $correctOrder = array($class5, $class1, $class2, $class3, $class4); $this->assertSame($correctOrder, $sorted); } -} \ No newline at end of file +} + +class NodeClass1 {} +class NodeClass2 {} +class NodeClass3 {} +class NodeClass4 {} +class NodeClass5 {} \ No newline at end of file