Array2Nested v1.1.2
Auteur: Roland - 27 december 2008 - 15:18 - Gekeurd door: Stijn - Hits: 2975 - Aantal punten: 4.50 (4 stemmen)
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.deva...xample.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
|
Code: |
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(' » ', $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() . ')';
}
?>
<?php require 'Nested.php'; // forum data whatever 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::KEY_ID => 'myID', Nested::KEY_PARENT => 'myParent' )); // nu nesten! $nested->nest(); // tijdsduur // 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()) { foreach($child->getAncestors(true/*reverse*/) as $ancestor) { $path[] = ($ancestor->isRoot() ? 'Root' : $ancestor->title); } echo '<strong>Pad:</strong> ' . implode(' » ', $path) . '<br />'; } echo '<strong>Diepte:</strong> ' . $child->getDepth() . '<br />'; if(!$child->hasChildren()) { echo 'Dit kind heeft geen sub kinderen.'; } else { // 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>'; } } // 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>'; } $nested->getRoot(), // bij de root beginnen $child->getId() // huidige id opvragen ); // getChildren & Flags if($child->hasChildren()) { // 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;
}
}
?>
<?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 */ /** * Identificatie waardes volgens code * * @var 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) { foreach($ident as $type => &$value) { if(!isset(self::$idents[$type])) { throw new Nested_Exception('Invalid ident type. (' . (string) $type . ')'); } $value = (int) $value; } self::$idents[$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(); } } 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(); return false; } return $this->_getById($args[0]); } } /** * 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() { foreach($this->_data as $index => &$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]; $id = (int) $id; } $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() { 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;
}
}
?>
<?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 $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]; continue; } } elseif(isset($flags[self::FLAG_EXCLUDE])) { $flag = $flags[self::FLAG_EXCLUDE]; continue; } } $return = $child; if(isset($flags[self::FLAG_RETURN])) { if($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': break; } } } } if(isset($flags[self::FLAG_CALLBACK]) && is_callable($flags[self::FLAG_CALLBACK])) { } $result[] = $return; } if(isset($flags[self::FLAG_SHUFFLE]) && $count > 1) { } if(isset($flags[self::FLAG_LIMIT])) { $limit = $flags[self::FLAG_LIMIT]; if(is_int($limit) && $limit < $count) { } } 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->_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 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() ); } 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}; } } 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 { }
?>
<?php /** * Nested_Exception * * Vang excepties op van deze module * * @package Nested */ class Nested_Exception extends Exception { } ?>
Download code (.txt)
|
|
|
Stemmen |
Niet ingelogd. |
|