Created
March 4, 2014 23:24
-
-
Save zbycz/9357900 to your computer and use it in GitHub Desktop.
Closure tree implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Class for smart browsing of recursive structures | |
* | |
* example: | |
* {var $node = $rootNode} | |
* <ul> | |
* {block #groups} | |
* <li n:foreach="$node->getChildren() as $node"> | |
* <div>{$node->name}</div> | |
* <ul n:if="$node->hasChildren()"> | |
* {include #groups, node => $node} | |
* </ul> | |
* </li> | |
* {/block} | |
* </ul> | |
* | |
* @author Pavel Zbytovský <[email protected]> | |
* @copyright (c) 2013 under CC-BY-SA | |
*/ | |
class TreeNode extends \Nette\Object | |
{ | |
private $nodesByParent; | |
private $data; | |
public $level; | |
/** | |
* Constructor | |
* | |
* @param ArrayAccess $data current node-data | |
* @param array $nodesByParent needs sub-items indexed by parent-id | |
* @throws \Nette\InvalidStateException | |
*/ | |
public function __construct($data, array $nodesByParent) | |
{ | |
if(!isset($data['id'])){ | |
throw new \Nette\InvalidStateException("TreeNode data need 'id' item!"); | |
} | |
$this->data = (array)$data; | |
$this->nodesByParent = $nodesByParent; | |
} | |
/** | |
* Returns node-data item | |
* | |
* @param string $name | |
* @return mixed | |
*/ | |
public function &__get($name) | |
{ | |
if(isset($this->data[$name])) { | |
return $this->data[$name]; | |
} | |
return parent::__get($name); | |
} | |
/** | |
* Checks if current node has children | |
* | |
* @return bool | |
*/ | |
public function hasChildren() | |
{ | |
return isset($this->nodesByParent[$this->id]); | |
} | |
/** | |
* Returns children of current node | |
* | |
* @return \Projectisimo\Models\TreeNode | |
*/ | |
public function getChildren() | |
{ | |
$results = array(); | |
if ($this->hasChildren()) { | |
foreach($this->nodesByParent[$this->id] as $row) { | |
$results[] = new TreeNode($row, $this->nodesByParent); | |
} | |
} | |
return $results; | |
} | |
/** | |
* Returns all descendants with correct level set (DFS algorithm) | |
* | |
* @param int $maxDepth | |
* @return array of TreeNodes | |
*/ | |
public function getDescendantsFlat($maxDepth = 100) | |
{ | |
$this->level = 0; | |
$flatResult = array(); | |
$stack = array($this); //process own children | |
while( ($parent = array_pop($stack)) ){ //recursion using stack | |
if($parent != $this) | |
$flatResult[] = $parent; | |
if($parent->level >= $maxDepth) break; | |
$children = $parent->getChildren(); | |
foreach(array_reverse($children) as $child){ | |
$child->level = $parent->level + 1; | |
$stack[] = $child; | |
} | |
} | |
return $flatResult; | |
} | |
/** | |
* Returns pairs useful for selectboxes | |
* | |
* @param int $maxDepth | |
* @return array of id=>name | |
*/ | |
public function getDescendantsFlatPairs($maxDepth = 100){ | |
$flat = $this->getDescendantsFlat($maxDepth); | |
$result = array(); | |
foreach($flat as $n){ | |
$result[$n->id] = $n->padding . $n->name; | |
} | |
return $result; | |
} | |
/** | |
* Gets level padding | |
*/ | |
public function getPadding($separator = " ") | |
{ | |
return ($separator ? str_repeat($separator, $this->level - 1) : ''); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Model | |
{ | |
/** | |
* Returns virtual root as TreeNode for easy ul-li displaying | |
* | |
* @return \TreeNode | |
*/ | |
public function getRootNode($user = NULL) | |
{ | |
$query = $this->db->query(" | |
SELECT *, COUNT(DISTINCT u2u.ancestor) AS count | |
FROM data d | |
LEFT JOIN user2user u2u | |
ON d.id = u2u.group | |
%if",$user," AND u2u.descendant = %i",$user," AND u2u.descendant != u2u.ancestor %end | |
GROUP BY d.id | |
ORDER BY name"); | |
$nodesByParent = $query->fetchAssoc('parent[]'); | |
$virtualRoot = array( | |
'id' => 0, | |
'parent' => -1, | |
'name' => 'virtual root' | |
); | |
return new TreeNode($virtualRoot, $nodesByParent); //virtual super root | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment