Scripts

PHP5 Array2Nested v1.1.2

Ik las op PHPFreakz onlangs een topic waarin gevraagd werd hoe je data kan omzetten naar geneste data zodat je bv categorieën eindeloos kunt nesten in een forum, webshop etc. Met dat idee en te veel vrije tijd tijdens de kerstdagen ben ik begonnen aan een class/module om het hele recursieve nesten ietwat te automatiseren. Het nesten gebeurd zoals genoemd recursief a.d.h.v. een id/parent combinatie binnen een array. Ondanks dat ik dat ik tijdens het maken van deze class pas op de hoogte werd gesteld van de left/right methode (http://www.sitepoint.com/article/hierarchical-data-database/2/), heb ik toch besloten om aan de recursieve methode vast te houden. Dit maakt het werken met objecten van _directe_ kinderen veel eenvoudiger. Daarnaast is het in de praktijk onwaarschijnlijk dat je tot diepte 1000+ gaat nesten. Uiteraard mogen mensen zich melden die zich interesseren voor het implementeren van een layer optie in deze module. Iets als; abstract Nested_Nest, Nested_Nest_Recursive, Nested_Nest_LeftRight Het is uiteraard wel essentieel dat de recursieve werking van de module blijft werken. (Nested::getRoot()->firstChild()->firstChild()[->...]) Getest op: PHP 5.2.4 Versie: 1.1.2 Bestanden: Nested_Example.php, Nested.php, Nested_Child.php, Nested_Exception.php Output voorbeeld: http://www.devarea.nl/roland-tmp/Nested_Example.png Geplaatst op: PHPFreakz, PHPHulp, Sitemasters Changelog: v1.1.2 [30-12-08] * Nested::sortAll toegevoegd om de kinderen van elk item tegelijk hetzelfde te sorteren * Nested_Child::getIndex() toegevoegd * Nested_Child::randomChild/childById/childByIndex toegevoegd * Nested_Child::isChild/getChild verwijderd * Nested_Output verwijderd; HTML implementatie is niet te globaliseren - opnieuw voorbeeld toegevoegd voor geneste HTML list v1.1.1 [28-12-08] * Nested::getFlat() aangepast; Nested::getFlat() -> array(id => Nested_Child); Nested::getFlat(true) -> array(id => array(row_data)) * Nested::MAX_DEPTH ident toegevoegd; elk kind lager als n bepaalde diepte wordt kind van de vorige parent (0 = unlimited) * Performance update: diepte wordt niet meer berekend door de 'ancestors' te tellen maar direct bij het nesten * Nested_Child::isMaxDepth() toegevoegd om te controleren of een kind zich in de maximale diepte bevindt * Kinderen sorteren gebeurd nu via het 'natural ordering algorithm', zodat 'Titel 10' na 'Titel 9' komt (ASC) v1.1.0 [27-12-08] * Nested_Child::FLAG_LIMIT toegvoegd om het aantal kinderen via Nested_Child::getChildren() te limieten * Nested::__toString/toHtml, Nested_Child::__toString/toHtml en Nested_Output toegvoegd (zie voorbeeld) v1.0.0 [26-12-08] * dump

php5-array2nested-v112
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 { }
?>

Reacties

0
Nog geen reacties.