Nested_Example.php
<?php
require 'Nested.php';

// forum data whatever
$data = array(
	array('myID' => 1, 'myParent' => 0, 'title' => 'Titel #1'),		// level 1
	array('myID' => 2, 'myParent' => 1, 'title' => 'Titel #2'),		// level 1.1
	array('myID' => 3, 'myParent' => 1, 'title' => 'Titel #3'),		// level 1.2
	array('myID' => 4, 'myParent' => 3, 'title' => 'Titel #4'),		// level 1.2.1
	array('myID' => 5, 'myParent' => 3, 'title' => 'Titel #5'),		// level 1.2.2
	array('myID' => 6, 'myParent' => 3, 'title' => 'Titel #6'),		// level 1.2.3
	array('myID' => 7, 'myParent' => 6, 'title' => 'Titel #7'),		// level 1.2.3.1
	array('myID' => 8, 'myParent' => 7, 'title' => 'Titel #8'),		// level 1.2.3.1.1
	array('myID' => 9, 'myParent' => 8, 'title' => 'Titel #9'),		// level 1.2.3.1.1.1
	array('myID' => 10, 'myParent' => 9, 'title' => 'Titel #10')	// level 1.2.3.1.1.1.1
);

try {
	// object aanmaken, maar nog niet nesten
	$nested = new Nested($data, false);

	// identificatie steutels instellen
	$nested->setIdent(array(
		Nested::KEY_ID     => 'myID',
		Nested::KEY_PARENT => 'myParent'
	));

	// nu nesten!
	$nested->nest();

	// tijdsduur
	echo 'Nesten duurde ' . number_format($nested->getParsetime(), 6);

	// haal alle items op
	$flat_object = $nested->getFlat(); // array(id => Nested_Child)
	$flat_array  = $nested->getFlat(true); // array(id => array('myId' => .., [..]))

	// alles sorteren, zet kinderen met meeste sub kinderen bovenaan
	$nested->sortAll(Nested_Child::SORT_CHILDNUM, Nested_Child::SORT_DESC);

	// bouw een simpele app
	$child = isset($_GET['id']) && $nested->isValidId($_GET['id'])
		? $nested->getById($_GET['id'])
		: $nested->getRoot();
	echo '<h1>' . ($child->isRoot() ? 'Root' : $child->title) . '</h1>';
	if(!$child->isRoot()) {
		$path = array();
		foreach($child->getAncestors(true/*reverse*/) as $ancestor) {
			$path[] = ($ancestor->isRoot() ? 'Root' : $ancestor->title);
		}
		echo '<strong>Pad:</strong> ' . implode(' &raquo; ', $path) . '<br />';
	}
	echo '<strong>Diepte:</strong> ' . $child->getDepth() . '<br />';
	if(!$child->hasChildren()) {
		echo 'Dit kind heeft geen sub kinderen.';
	} else {
		echo '<ul>';
		// kinderen zijn al gesorteerd volgens SORT_CHILDNUM, SORT_DESC
		foreach($child->getChildren() as $subchild) {
			echo '<li><a href="?id=' . $subchild->getId() . '">' . $subchild->title . '</a> (' . $subchild->numChildren() . ')</li>';
		}
		echo '</ul>';
	}

	// een complete structuur tonen
	echo '<h1>Boom structuur</h1>';
	function output_nested($child, $current_id) {
		$html = '<ul>';
		if($child->hasChildren()) {
			foreach($child->getChildren() as $subchild) {
				$title = $subchild->title;
				$title = $current_id == $subchild->getId()
					? '<strong>' . $title . '</strong>'
					: '<a href="?id=' . $subchild->getId() . '">' . $title . '</a>';
				$title .= ' (' . $subchild->numChildren() . ')';
				$html  .= '<li>' . $title . output_nested($subchild, $current_id) . '</li>';
			}
		}
		return $html . '</ul>';
	}
	echo output_nested(
		$nested->getRoot(), // bij de root beginnen
		$child->getId()     // huidige id opvragen
	);

	// getChildren & Flags
	if($child->hasChildren()) {
		$flags = array();

		// exclude specifieke id's
		$flags[Nested_Child::FLAG_EXCLUDE] = 1; // alle kinderen behalve id 1
		// variant: $flags[Nested_Child::FLAG_EXCLUDE] = array(1,2); // alle kinderen behalve id 1 en 2

		// _OF_: include specifieke id's
		$flags[Nested_Child::FLAG_INCLUDE] = 1; // alleen kind ophalen als id 1 is
		// variant: $flags[Nested_Child::FLAG_INCLUDE] = array(2,3); // alleen kind ophalen als id 1 of 2 is

		// return enkel kind eigenschap/methode
		$flags[Nested_Child::FLAG_RETURN] = 'title'; // eigenschap kind returnen
		// variant: $flags[Nested_Child::FLAG_RETURN] = 'numChildren'; // methode kind returnen
	
		// gebruikers callback toepassen
		function test($child) {
			// waarde $child afhankelink van return flag, standaard Nested_Child object
			// in dit voorbeeld bevat $child dus de titel
			return 'De titel: ' . $child;
		}
		$flags[Nested_Child::FLAG_CALLBACK] = 'test'; // gebruikersfunctie toepassen
		// variant: $flags[Nested_Child::FLAG_CALLBACK] = array($object, 'methode'); // methode toepassen

		// kinderen door elkaar schudden
		$flags[Nested_Child::FLAG_SHUFFLE] = true; // zet de kinderen in random volgorde

		// kinderen limieten
		$flags[Nested_Child::FLAG_LIMIT] = 2; // eerste 2 kinderen teruggeven

		// flags toepassen
		foreach($child->getChildren($flags) as $child_or_return_or_callback_value) {
			// where the REAL magic happens ;]
		}
	}

	// firstChild, lastChild, randomChild, childByIndex, childById
	if(false !== ($first_child = $child->firstChild())) {
		echo 'Eerste kind: ' . $first_child->title . '<br />';
	}
	if(false !== ($last_child = $child->lastChild())) {
		echo 'Laatste kind: ' . $last_child->title . '<br />';
	}
	if(false !== ($random_child = $child->randomChild())) {
		echo 'Random kind: ' . $random_child->title . '<br />';
	}
	if(false !== ($second_child = $child->childByIndex(1))) {
		echo 'Tweede kind: ' . $second_child->title . '<br />';
	}
	if(false !== ($id_2_child = $child->childById(2))) {
		echo 'ID #2 kind: ' . $id_2_child->title . '<br />';
	}

	// sortAll, sortChildren
	$nested->sortAll('title'/*, Nested_Child::SORT_DESC*/); // alle kinderen van elk item in de geneste data zijn gesorteerd op titel
	$child->sortChildren('title'/*, Nested_Child::SORT_DESC*/); // de kinderen van het huidige item zijn gesorteerd volgens titel
	// ga verder met Nested_Child::getChildren e.d., de ingestelde volgorde blijft van toepassing
} catch(Nested_Exception $e) {
	echo $e->getMessage() . ' (' . $e->getFile() . ', line ' . $e->getLine() . ')';
}
?>

Nested.php
<?php
require 'Nested_Exception.php';
require 'Nested_Child.php';
 
/**
 * Nested
 *
 * @version	1.1.2
 * @author	Roland Franssen
 * @email	franssen[dot]roland[at]gmail[dot]com
 */

/**
 * Nested
 *
 * Object die een geneste set vasthoudt van alle kinderen
 *
 * @package	Nested
 */
class Nested {
	/**
	 * Ident codes
	 *
	 * @define	KEY_ID		Identificeer ID sleutel
	 * @define	KEY_PARENT	Identificeer moeder sleutel
	 * @define	ROOT_ID		Identificeer root ID
	 * @define	MAX_DEPTH	Identificeer maximale diepte
	 */
	const KEY_ID      = 0;
	const KEY_PARENT  = 1;
	const ROOT_ID     = 2;
	const MAX_DEPTH   = 3;

	/**
	 * Geneste kinderen
	 *
	 * @var	null|array
	 */
	static protected $rows;
	/**
	 * Identificatie waardes volgens code
	 *
	 * @var array
	 */
	static protected $idents = array(
		self::KEY_ID     => 'id',
		self::KEY_PARENT => 'parent',
		self::ROOT_ID    => 0,
		self::MAX_DEPTH  => 0
	);

	/**
	 * Rauwe input data
	 *
	 * @var	null|array
	 */
	private $_data;
	/**
	 * Parse tijd van het nesten
	 *
	 * @var	null|float
	 */
	private $_time;

	/**
	 * Initialiseer een nieuwe geneste set
	 *
	 * @param	$data		array	Rauwe data volgends id/parent combinatie
	 * @param	$auto_nest	boolean	De data direct nesten
	 * @return	instance
	 */
	public function __construct(array $data, $auto_nest = true) {
		$this->_data = $data;
		if($auto_nest === true) {
			$this->nest();
		}
	}

	/**
	 * Stel identificatie in
	 *
	 * @param	$ident	mixed	Waarde van identificatie(s)
	 * @param	$type	{@link Nested::KEY_ID}
	 *					{@link Nested::KEY_PARENT}
	 *					{@link Nested::ROOT_ID}
	 * @return	Nested
	 */
	public function setIdent($ident, $type = null) {
		$ident = !is_array($ident) ? array($type => $ident) : $ident;
		foreach($ident as $type => &$value) {
			if(!isset(self::$idents[$type])) {
				throw new Nested_Exception('Invalid ident type. (' . (string) $type . ')');
			}
			if(ctype_digit($value)) {
				$value = (int) $value;
			}
			self::$idents[$type] = $value;
		}
		unset($type, $value);
		return $this;
	}

	/**
	 * Verkrijg de parse tijd van het nesten
	 *
	 * @return	float
	 */
	public function getParsetime() {
		$this->_isNested();
		return $this->_time;
	}

	/**
	 * Verkrijg een platte structuur van de data array(id => Nested_Child)
	 *
	 * @return	array
	 */
	public function getFlat($to_array = false) {
		$this->_isNested();
		$rows = self::$rows;
		unset($rows[self::$idents[self::ROOT_ID]]);
		if($to_array) {
			foreach($rows as &$row) {
				$row = $row->getRow();
			}
			unset($row);
		}
		return $rows;
	}

	/**
	 * Controleer of het ID bestaat in de structuur
	 *
	 * @param	$id		mixed	ID van kind
	 * @return	boolean
	 */
	public function isValidId($id) {
		$this->_isNested();
		return $id != self::$idents[self::ROOT_ID] && isset(self::$rows[$id]);
	}

	/**
	 * Verkrijg een kind middels ID
	 * getById(id[, id, [..]]) | getById(array(id[, id, [..]]))
	 *
	 * @return false|array|Nested_Child
	 */
	public function getById() {
		$this->_isNested();
		$args = func_get_args();
		if(!isset($args[0])) {
			return false;
		}
		if(!is_array($args[0]) && !isset($args[1])) {
			return $this->_getById($args[0]);
		}
		return array_map(array($this, '_getById'), (is_array($args[0]) ? $args[0] : $args));
	}

	/**
	 * Verkrijg het root kind
	 *
	 * @param	$children	boolean	Return direct de kinderen
	 * @return	false|array|Nested_Child
	 */
	public function getRoot($children = false) {
		$this->_isNested();
		$root_id = self::$idents[self::ROOT_ID];
		if(isset(self::$rows[$root_id])) {
			return ($root = self::$rows[$root_id]) && $children !== false ? $root->getChildren() : $root;
		}
		return false;
	}

	/**
	 * Sorteer kinderen van elk item
	 *
	 * @param	$key	mixed	Sleutel uit originele data of {@link Nested_Child::SORT_CHILDNUM}
	 * @param	$mode	{@link Nested_Child::SORT_ASC}
	 *					{@link Nested_Child::SORT_DESC}
	 * @return Nested
	 */
	public function sortAll($key, $mode = Nested_Child::SORT_ASC) {
		$this->_isNested();
		foreach(self::$rows as $child) {
			$child->sortChildren($key, $mode);
		}
		return $this;
	}

	/**
	 * Maak van rauwe data een geneste set
	 *
	 * @return	Nested
	 */
	public function nest() {
		$this->_time = microtime(false);
		self::$rows  = array();
		list($key_id, $key_parent, $root_id, $max_depth) = array_values(self::$idents);
		foreach($this->_data as $index => &$row) {
			if(!is_array($row)) {
				throw new Nested_Exception('Array index (' . $index . ') is not an array.');
			}
			if(!isset($row[$key_id]) || !isset($row[$key_parent])) {
				throw new Nested_Exception('Key ' . $key_id . ' or ' . $key_parent . ' does not exists in index ' . $index . '.');
			}
			$id     = &$row[$key_id];
			$parent = &$row[$key_parent];
			if(ctype_digit($id)) {
				$id = (int) $id;
			}
			if(ctype_digit($parent)) {
				$parent = (int) $parent;
			}
			if($id === $parent) {
				throw new Nested_Exception('Key ' . $key_id . ' cannot be the same as ' . $key_parent . ' in index ' . $index . '.');
			}
			if(isset(self::$rows[$id])) {
				throw new Nested_Exception('Key ' . $key_id . ' should be unique in index ' . $index . '.');
			}
			if(!isset(self::$rows[$parent])) {
				self::$rows[$parent] = new Nested_Child($parent);
			}
			$depth = (self::$rows[$parent]->getDepth() + 1);
			if(is_int($max_depth) && $max_depth > 0 && $depth > $max_depth) {
				$depth  = $max_depth;
				$parent = self::$rows[$parent]->getParent()->getId();
			}
			self::$rows[$id] = new Nested_Child($id, $parent, $row);
			self::$rows[$id]->setDepth($depth);
			self::$rows[$parent]->addChild($id, self::$rows[$id]);
		}
		if(!isset(self::$rows[$root_id])) {
			throw new Nested_Exception('Root key missing. (' . $root_id . ').');
		}
		$this->_time = (microtime(false) - $this->_time);
		unset($this->_data, $index, $row, $id, $parent, $depth);
		return $this;
	}

	/**
	 * Methode voor ID transformatie
	 *
	 * @param	$id	mixed	ID van kind
	 * @return	false|Nested_Child
	 */
	private function _getById($id) {
		if($id instanceof Nested_Child) {
			return $id;
		}
		if($id == self::$idents[self::ROOT_ID]) {
			return false;
		}
		$rows = self::$rows;
		return isset($rows[$id]) ? $rows[$id] : false;
	}

	/**
	 * Controleer of rauwe data genest is
	 *
	 * @return	boolean
	 */
	private function _isNested() {
		if(!is_array(self::$rows)) {
			throw new Nested_Exception('Data is not nested.');
		}
		return true;
	}
}
?>

Nested_Child.php
<?php
/**
 * Nested_Child
 *
 * Uniek kind object uit de geneste set
 *
 * @package	Nested
 */
class Nested_Child extends Nested {
	/**
	 * Sorteer codes voor sortChildren
	 *
	 * @define	SORT_ASC		Sorteer kinderen aflopend
	 * @define	SORT_DESC		Sorteer kinderen oplopend
	 * @define	SORT_CHILDNUM	Sorteer kinderen op aantal sub kinderen
	 */
	const SORT_ASC		= 10;
	const SORT_DESC		= 11;
	const SORT_CHILDNUM = 12;

	/**
	 * Flag codes voor getChildren
	 *
	 * @define	FLAG_EXCLUDE	Filter kind uit resultaat
	 * @define	FLAG_INCLUDE	Filter kind in resultaat
	 * @define	FLAG_RETURN		Filter kind eigenschap/methode in resultaat
	 * @define	FLAG_CALLBACK	Filter user callback in resultaat
	 * @define	FLAG_SHUFFLE	Verkrijg kinderen in random volgorde
	 * @define	FLAG_LIMIT		Limiet het aantal kinderen
	 */
	const FLAG_EXCLUDE	= 30;
	const FLAG_INCLUDE	= 31;
	const FLAG_RETURN	= 32;
	const FLAG_CALLBACK = 33;
	const FLAG_SHUFFLE  = 34;
	const FLAG_LIMIT	= 35;

	/**
	 * Uniek ID van kind
	 *
	 * @var	mixed
	 */
	private $_id;
	/**
	 * Moeder ID van kind
	 *
	 * @var	mixed
	 */
	private $_parent;
	/**
	 * Originele data van kind
	 *
	 * @var	null|false|array
	 */
	private $_row;
	/**
	 * Sorteer sleutel voor data
	 *
	 * @var	mixed
	 */
	private $_sort;
	/**
	 * Aantal sub kinderen van kind
	 *
	 * @var	integer
	 */
	private $_count = 0;
	/**
	 * Sub kinderen van kind (id => kind)
	 *
	 * @var	array
	 */
	private $_children	= array();
	/**
	 * Sub kinderen volgens index (index => id)
	 *
	 * @var	array
	 */
	private $_indexed   = array();
	/**
	 * De ouders van het kind tot aan de root
	 *
	 * @var	null|array
	 */
	private $_ancestors;
	/**
	 * De naaste kinderen exclusief het huidige kind
	 *
	 * @var	null|array
	 */
	private $_siblings;
	/**
	 * Diepte van het kind t.o.v. de root
	 *
	 * @var	integer
	 */
	private $_depth = -1;

	/**
	 * Initialiseer het kind met basis eigenschappen waarbij de root slechts een ID heeft
	 *
	 * @param	$id		mixed	Uniek ID
	 * @param	$parent	mixed	Moeder ID
	 * @param	$row	mixed	Originele data
	 * @return	instance
	 */
	public final function __construct($id, $parent = false, $row = false) {
		$this->_id     = $id;
		$this->_parent = $parent;
		$this->_row    = $row;
	}

	/**
	 * Wanneer property van kind gevraagd wordt, zoeken in originele data
	 * Nested_Child->title	=>	Nested_Child->$_row[title]
	 *
	 * @param	$key	string	Eigenschap die gevraagd wordt
	 * @return	mixed
	 */
	public function __get($key) {
		if($this->_row === false || !isset($this->_row[$key])) {
			throw new Nested_Exception('Invalid property (' . (string) $key . ') in ' . __CLASS__ . '.');
		}
		return $this->_row[$key];
	}

	/**
	 * Voeg sub kind toe aan het huidige kind
	 *
	 * @param	$id		mixed			Uniek ID
	 * @param	$child	Nested_Child	Object van kind middels referentie
	 * @return	Nested_Child
	 */
	protected function addChild($id, Nested_Child &$child) {
		$this->_children[$this->_count] = $child;
		$this->_indexed[$id]            = $this->_count;
		++$this->_count;
		return $this;
	}

	/**
	 * Stel diepte in van kind
	 *
	 * @param	integer	$depth	Diepte van kind
	 * @return	Nested_Child
	 */
	protected function setDepth($depth) {
		$this->_depth = (int) $depth;
		return $this;
	}

	/**
	 * Verkrijg diepte van kind t.o.v. de root waarbij de root zelf -1 is (eerste kind dus 0)
	 *
	 * @return	integer
	 */
	public function getDepth() {
		return $this->_depth;
	}

	/**
	 * Controleer of kind zich in de maximale diepte bevindt
	 *
	 * @return	boolean
	 */
	public function isMaxDepth() {
		$depth = $this->getDepth();
		return $depth > 0 && $depth === (int) parent::$idents[Nested::MAX_DEPTH];
	}

	/**
	 * Controleer of het huidige kind de root is
	 *
	 * @return	boolean
	 */
	public function isRoot() {
		return $this->getId() === parent::$idents[Nested::ROOT_ID];
	}

	/**
	 * Verkrijg ID van kind
	 *
	 * @return	mixed
	 */
	public function getId() {
		return $this->_id;
	}

	/**
	 * Verkrijg moeder ID van kind
	 *
	 * @return	mixed
	 */
	public function getParentId() {
		return $this->_parent;
	}

	/**
	 * verkrijg index van kind t.o.v. van overige kinderen
	 *
	 * @return	false|integer
	 */
	public function getIndex() {
		if(false !== ($parent = $this->getParent())) {
			return $parent->_indexed[$this->getId()];
		}
		return false;
	}

	/**
	 * Verkrijg originele data van kind
	 *
	 * @return false|array
	 */
	public function getRow() {
		return $this->_row;
	}

	/**
	 * Verkrijg moeder object van kind
	 *
	 * @return	false|Nested_Child
	 */
	public function getParent() {
		if($this->isRoot()) {
			return false;
		}
		if($this->getParentId() === parent::$idents[Nested::ROOT_ID]) {
			return parent::getRoot();
		}
		return parent::getById($this->getParentId());
	}

	/**
	 * Controleer of het kind sub kinderen heeft
	 *
	 * @return	boolean
	 */
	public function hasChildren() {
		return $this->_count > 0;
	}

	/**
	 * Verkrijg het aantal sub kinderen
	 *
	 * @return	integer
	 */
	public function numChildren() {
		return $this->_count;
	}

	/**
	 * Verkrijg een kind middels ID
	 *
	 * @param	$id	mixed	Uniek ID van kind
	 * @return	false|Nested_Child
	 */
	public function childById($id) {
		return isset($this->_indexed[$id]) ? $this->_children[$this->_indexed[$id]] : false;
	}

	/**
	 * Verkrijg een kind middels index
	 *
	 * @param	$index	integer	Index van kind
	 * @return	false|Nested_Child
	 */
	public function childByIndex($index) {
		return isset($this->_children[$index]) ? $this->_children[$index] : false;
	}

	/**
	 * Verkrijg het eerste sub kind
	 *
	 * @return	false|Nested_Child
	 */
	public function firstChild() {
		return $this->childByIndex(0);
	}

	/**
	 * Verkrijg het laatste sub kind
	 *
	 * @return	false|Nested_Child
	 */
	public function lastChild() {
		return $this->childByIndex($this->numChildren() - 1);
	}
	
	/**
	 * Verkrijg random kind
	 *
	 * @return	false|Nested_Child
	 */
	public function randomChild() {
		return $this->hasChildren()
			? ($this->numChildren() === 1
				? $this->firstChild()
				: $this->childByIndex(mt_rand(0, ($this->numChildren() - 1))))
			: false;
	}

	/**
	 * Verkrijg sub kinderen
	 *
	 * @param	$flags	array	Array met flags volgens array({@link Nested_Child::FLAG_*} => waarde)
	 * @return	array
	 */
	public function getChildren($flags = null) {
		if(!$this->hasChildren()) {
			return array();
		}
		if(!isset($flags)) {
			return $this->_children;
		}
		for($i = 0, $result = array(); isset($this->_children[$i]); ++$i) {
			$child = $this->_children[$i];
			$id    = $child->getId();
			if(isset($flags[self::FLAG_INCLUDE])) {
				$flag = $flags[self::FLAG_INCLUDE];
				if((!is_array($flag) && $id != $flag) || (is_array($flag) && !in_array($id, $flag))) {
					continue;
				}
			} elseif(isset($flags[self::FLAG_EXCLUDE])) {
				$flag = $flags[self::FLAG_EXCLUDE];
				if((!is_array($flag) && $id == $flag) || (is_array($flag) && in_array($id, $flag))) {
					continue;
				}
			}
			$return = $child;
			if(isset($flags[self::FLAG_RETURN])) {
				$method = strtolower(trim((string) $flags[self::FLAG_RETURN]));
				if($method <> '') {
					if(!method_exists($child, $method)) {
						$return = $child->{$method};
					} else {
						switch($method) {
							case 'getid':       case 'getparentid': case 'getindex':
							case 'getdepth':    case 'getrow':      case 'getparent':
							case 'firstchild':  case 'lastchild':   case 'randomchild':
							case 'haschildren': case 'numchildren':
								$return = call_user_func(array($child, $method));
								break;
						}
					}
				}
			}
			if(isset($flags[self::FLAG_CALLBACK]) && is_callable($flags[self::FLAG_CALLBACK])) {
				$return = call_user_func($flags[self::FLAG_CALLBACK], $return);
			}
			$result[] = $return;
		}
		unset($return);
		$count = count($result);
		if(isset($flags[self::FLAG_SHUFFLE]) && $count > 1) {
			shuffle($result);
		}
		if(isset($flags[self::FLAG_LIMIT])) {
			$limit = $flags[self::FLAG_LIMIT];
			if(is_int($limit) && $limit < $count) {
				$result = array_slice($result, 0, $limit);
			}
		}
		return $result;
	}

	/**
	 * Sorteer kinderen
	 *
	 * @param	$key	mixed	Sleutel uit originele data of {@link Nested_Child::SORT_CHILDNUM}
	 * @param	$mode	{@link Nested_Child::SORT_ASC}
	 *					{@link Nested_Child::SORT_DESC}
	 * @return Nested_Child
	 */
	public function sortChildren($key, $mode = self::SORT_ASC) {
		if($this->hasChildren()) {
			$this->_sort = $key;
			usort($this->_children, array($this, '_sortCmp'));
			$this->_resetIndexation();
			for($i = 0; isset($this->_children[$i]); $this->_indexed[$this->_children[$i]->getId()] = $i, ++$i);
		}
		if($mode === self::SORT_DESC) {
			$this->_children = array_reverse($this->_children);
			$this->_resetIndexation();
		}
		return $this;
	}

	/**
	 * Verkrijg naaste kinderen behoudens het huidige kind
	 *
	 * @return false|array
	 */
	public function getSiblings() {
		if($this->isRoot()) {
			return false;
		}
		if(!isset($this->_siblings)) {
			$this->_siblings = array();
			$parent = $this->getParent();
			if($parent->numChildren() > 1) {
				$this->_siblings = $parent->getChildren(array(
					self::FLAG_RETURN  => 'getId',
					self::FLAG_EXCLUDE => $this->getId()
				));
			}
		}
		if(!isset($this->_siblings[0])) {
			return array();
		}
		return parent::getById($this->_siblings);
	}

	/**
	 * Verkrijg ouders tot aan de root
	 *
	 * @param	$reverse	boolean	Zet eerste ouder vooraan
	 * @param	$root		boolean	Voeg root toe
	 * @return	false|array
	 */
	public function getAncestors($reverse = false, $root = true) {
		if($this->isRoot()) {
			return false;
		}
		if(!isset($this->_ancestors)) {
			for(
				$scope = $this, $this->_ancestors = array();
				($parent = $scope->getParent()) !== false && !$parent->isRoot();
				$scope = $parent, $this->_ancestors[] = $scope->getId()
			);
			unset($scope, $parent);
		}
		if($root) {
			$this->_ancestors[] = parent::getRoot();
		}
		return parent::getById(($reverse ? array_reverse($this->_ancestors) : $this->_ancestors));
	}

	/**
	 * Vergelijkings methode voor sorteren
	 *
	 * @param	$a	Nested_Child
	 * @param	$b	Nested_Child
	 * @return	integer
	 */
	private function _sortCmp($a, $b) {
		$sort = $this->_sort;
		if($sort === self::SORT_CHILDNUM) {
			$a = $a->numChildren();
			$b = $b->numChildren();
		} else {
			$a = $a->{$sort};
			$b = $b->{$sort};
		}
		if(!is_int($a) || !is_int($b)) {
			return strnatcmp((string) $a, (string) $b);
		}
		return $a === $b ? 0 : ($a < $b ? -1 : 1);
	}

	/**
	 * Reset de id naar index relatie
	 *
	 * @return	void
	 */
	private function _resetIndexation() {
		for(
			$i = 0;
			isset($this->_children[$i]);
			$this->_indexed[$this->_children[$i]->getId()] = $i, ++$i
		);
		return;
	}
}
?>

Nested_Exception.php
<?php
/**
 * Nested_Exception
 *
 * Vang excepties op van deze module
 *
 * @package	Nested
 */
class Nested_Exception extends Exception { }
?>