I assume that there will be a method that will take the identifiers of both the target and the target (or entire data objects, whatever) and check whether the target is in the destination path or not. If so, then the method will return false or throw exceptions, otherwise it will return true. This method can be introduced into your directory by copying the code stream as a control structure.
Personally for hierarchical data structures, I would include it as a nested set . This can be a problem for setting up and fixing a nested set can be tedious, but I find it very convenient for checking relationships between nodes and getting whole subtrees.
Here's a PHPUnit test with a partial and somewhat naive implementation of my idea:
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(-1); require 'vendor/autoload.php'; class ParentingSucks { public $data = array(); public function isAllowed($targetId, $destId) { $target = $this->getById($targetId); $dest = $this->getById($destId); $parent = $this->getById($dest['childOf']); $isAllowed = true; while ($parent) { if ($parent['id'] == $targetId) { $isAllowed = false; break; } $parent = $this->getById($parent['childOf']); } return $isAllowed; } public function getById($id) { if (isset($this->data[$id])) { return $this->data[$id]; } return array(); } } class HowIMetYourParentDir extends PHPUnit_Framework_TestCase { public function droppingOnParentNotAllowed($data, $target, $dest, $outcome) { $stub = $this->getMock('ParentingSucks', null); $stub->data = $data; $result = $stub->isAllowed($target, $dest); $this->assertEquals($result, $outcome, 'Oh no!'); } public function generate() { $fakeData = array( 1 => array('id' => 1, 'name' => 'A', 'childOf' => 0), 2 => array('id' => 2, 'name' => 'B', 'childOf' => 1), 3 => array('id' => 3, 'name' => 'C', 'childOf' => 0), 4 => array('id' => 4, 'name' => 'D', 'childOf' => 3), 5 => array('id' => 5, 'name' => 'E', 'childOf' => 2), 6 => array('id' => 6, 'name' => 'F', 'childOf' => 5), ); return array( array( $fakeData, 2,
Variable / function / class names are probably not suitable for your domain model, so ignore them.