Skip to content
This repository
Browse code

ENHANCEMENT: add an infinite-loop check as validation in Hierarchy (o…

…s4399)

Check only when the parent has changed - hierarchy traversal is
expensive operation, so we do it only when it is needed.
  • Loading branch information...
commit be97535b1ed45eaedcee1ccca5bf194054233008 1 parent 42e6ae2
Mateusz U authored April 13, 2012
2  model/DataExtension.php
@@ -70,7 +70,7 @@ public static function unload_extra_statics($class, $extension) {
70 70
 	 * @param $validationResult Local validation result
71 71
 	 * @throws ValidationException
72 72
 	 */
73  
-	function validate(ValidationResult &$validationResult) {
  73
+	function validate(ValidationResult $validationResult) {
74 74
 	}
75 75
 	
76 76
 	/**
32  model/Hierarchy.php
@@ -31,6 +31,38 @@ static function add_to_class($class, $extensionClass, $args = null) {
31 31
 	}
32 32
 
33 33
 	/**
  34
+	 * Validate the owner object - check for existence of infinite loops.
  35
+	 */
  36
+	function validate(ValidationResult $validationResult) {
  37
+		if (!$this->owner->ID) return; // The object is new, won't be looping.
  38
+		if (!$this->owner->ParentID) return; // The object has no parent, won't be looping.
  39
+		if (!$this->owner->isChanged('ParentID')) return; // The parent has not changed, skip the check for performance reasons.
  40
+
  41
+		// Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
  42
+		$node = $this->owner;
  43
+		while($node) {
  44
+			if ($node->ParentID==$this->owner->ID) {
  45
+				// Hierarchy is looping.
  46
+				$validationResult->error(
  47
+					sprintf(
  48
+						_t(
  49
+							'Hierarchy.InfiniteLoopNotAllowed',
  50
+							'Infinite loop found within the "%s" hierarchy. Please change the parent to resolve this',
  51
+							'First argument is the class that makes up the hierarchy.'
  52
+						),
  53
+						$this->owner->class
  54
+					),
  55
+					'INFINITE_LOOP'
  56
+				);
  57
+				break;
  58
+			}
  59
+			$node = $node->ParentID ? $node->Parent() : null;
  60
+		}
  61
+
  62
+		// At this point the $validationResult contains the response.
  63
+	}
  64
+
  65
+	/**
34 66
 	 * Returns the children of this DataObject as an XHTML UL. This will be called recursively on each child,
35 67
 	 * so if they have children they will be displayed as a UL inside a LI.
36 68
 	 * @param string $attributes Attributes to add to the UL.
19  tests/model/HierarchyTest.php
@@ -13,6 +13,25 @@ class HierarchyTest extends SapphireTest {
13 13
 	);
14 14
 
15 15
 	/**
  16
+	 * Test the Hierarchy prevents infinite loops.
  17
+	 */
  18
+	function testPreventLoop() {
  19
+		$obj2 = $this->objFromFixture('HierarchyTest_Object', 'obj2');
  20
+		$obj2aa = $this->objFromFixture('HierarchyTest_Object', 'obj2aa');
  21
+
  22
+		$obj2->ParentID = $obj2aa->ID;
  23
+		try {
  24
+			$obj2->write();
  25
+		}
  26
+		catch (ValidationException $e) {
  27
+			$this->assertContains('Infinite loop found within the "HierarchyTest_Object" hierarchy', $e->getMessage());
  28
+			return;
  29
+		}
  30
+
  31
+		$this->fail('Failed to prevent infinite loop in hierarchy.');
  32
+	}
  33
+
  34
+	/**
16 35
 	 * Test Hierarchy::AllHistoricalChildren().
17 36
 	 */
18 37
 	function testAllHistoricalChildren() {

0 notes on commit be97535

Please sign in to comment.
Something went wrong with that request. Please try again.