Last active
December 5, 2016 05:16
-
-
Save Jeff-Russ/f15aca220e83787309ebd2d62f10d60e to your computer and use it in GitHub Desktop.
This file contains hidden or 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 | |
| /** | |
| * Node is a smart array object inspired by the Node/Tree data structure and the | |
| * UNIX file system's inodes. Just a UNIX directory has a hidden file called | |
| * '..' in every directory which is a reference to the parent directory, a Node | |
| * object has $node['..'] which is a reference to the parent Node, or itself | |
| * if there is no parent Node meaning the Node is 'root', and it's $node['/'] | |
| * element which would also be reference to itself if root. | |
| * | |
| * Basically a Node is an array on steroids but why would you need this? Nodes | |
| * keep everything in reach in any scope without poluting the global namespace. | |
| * You can pass a single Node object into a callback and have it expand itself | |
| * to have access to objects "above" it or "below" it. You could actually have | |
| * an entire piece of software that is just one node object variable with every | |
| * piece of data being array elements and object references contained within it. | |
| * | |
| * There is a '.' key which is the contents of the Node's 'tree' (really just a | |
| * normal array) but it's not directly accessable. The layer of code between | |
| * you and the array ensures that any arrays/node added and removed to the node | |
| * are automatically configured to reflect the changing relationships. For | |
| * example, if you take a node that sits on one node and add it the key of | |
| * another, the old node will be told to unset it and the new node will set it's | |
| * parent and root properties to reflect it's new home. Arrays can be set to be | |
| * converted to full Node objects automatically. Nested Node's can be set not | |
| * be directly re-assignable, only their contents and their keys can be protected. | |
| * | |
| * See the keys on $_ ( $ # ~ ! .. / and . ) to get more info. | |
| */ | |
| class Node implements Serializable, IteratorAggregate, ArrayAccess, Countable | |
| { | |
| protected $_; | |
| public function __construct($array=[], $args=[]) { | |
| $this->_ = array_merge([ | |
| '$' =>'', # if nested, key on parent's _['.'] | |
| '#' => 0, # distance from root node | |
| '~' => true, # true/false/'shallow' array to node conversion | |
| '!' => 'nodes',# true/false/'nodes' prevent reassignment | |
| '..'=> $this, '/' => $this, '.'=> $array | |
| ], $args); | |
| if ( !empty($this->_['.']) ) self::_rConfig($this); | |
| } | |
| public function serialize(){ return serialize($this->_['.']); } | |
| public function unserialize($s_ized) { $this->_['.'] = unserialize($s_ized);} | |
| public function getIterator() { return new ArrayIterator( $this->_['.'] ); } | |
| public function count() { return count( $this->_['.'] ); } | |
| public function length(){ return $this->count(); } | |
| public function offsetExists($key){return array_key_exists($key,$this->_['.']);} | |
| public function eject($key) {return $this->offsetUnset($key);} | |
| public function offsetUnset($key) { | |
| if (array_key_exists($key, $this->_['.'])) { | |
| $v = $this->_['.'][$key]; | |
| if (is_a($v, get_class())) { | |
| $v->_['#'] = 0; | |
| $v->_['..']= $node; | |
| $v->_['/'] = $node; | |
| if ( !empty($v->_['.']) ) self::_rConfig($v); | |
| } | |
| unset( $this->_['.'][$key] ); return $v; | |
| } | |
| else return null; | |
| } | |
| public function offsetGet($key) | |
| { | |
| if (ctype_punct($key[0])) { | |
| if (array_key_exists($key,$this->_) && $key!=='.') return $this->_[$key]; | |
| elseif ($key==='/?') return $this->_['/']===$this; # is root? | |
| elseif ($key==='..$')return $this->_['..']->_['$'];# parent key | |
| elseif ($key==='/$') return $this->_['/']->_['$']; # root key | |
| elseif ($key==='[n]')return count($this->_['.']); # count | |
| } | |
| return array_key_exists( $key,$this->_['.'] ) ? $this->_['.'][$key] : null; | |
| } | |
| public function offsetSet($key, $val) | |
| { | |
| # modify node properties | |
| if (ctype_punct($key[0])) { | |
| # change key: | |
| if ($key==='$') { | |
| if ($this['/']===$this ) { $this['/'] = $val; return $this; } | |
| if ($this->keyStat($val)) return false; | |
| $parent =& $this->_['..']->_['.']; | |
| if ($parent[$this->_['$']]===$this) unset($parent[$this->_['$']]); | |
| $this->_['$'] = $val; | |
| $parent[$val] = $this; | |
| return $this; | |
| # change parent: | |
| } elseif ($key==='..') { | |
| if ($this->_['..']===$val) return false; | |
| elseif (!is_a($val, get_class())) return false; | |
| else { $this->_['.'][$key] = $val; self::_rConfig($this, $key); } | |
| return $this; | |
| # change protection level: | |
| } elseif ($key==='!') { $this->_['!'] = $val; | |
| # change conversion setting and apply: | |
| } elseif ($key==='~') { | |
| if ($val=null) return $this->_['~']; #getter mode | |
| elseif ($val===$this->_['~']) return $this; | |
| else $this->_['~'] = $val; | |
| if (!empty($this->_['.'])) self::_rConfig($this); | |
| return $this; | |
| } | |
| } | |
| if ( array_key_exists($key, $this->_['.']) ) { | |
| $prev_val = $this->_['.'][$key]; | |
| $is_node = is_a($prev_val, get_class()); | |
| if ( $this->_['!']!==false ) /* there is at least some protection */ { | |
| # anything but protection being false blocks nodes. true blocks all: | |
| if ($this->_['!']===true || $is_node) return false; | |
| # we can overwrite an array but only if protection is set to 'nodes' | |
| elseif (is_array($prev_val) && $this->_['!']!=='nodes') return false; | |
| # If it was a node we should formally remove it (make it root): | |
| if ($is_node) { | |
| $prev_val->_['#'] = 0; | |
| $prev_val->_['..']= $prev_val; | |
| $prev_val->_['/'] = $prev_val; | |
| if ( !empty($this->_['.'][$key]->_['.']) ) | |
| self::_rConfig( $this->_['.'][$key] ); | |
| } | |
| } | |
| unset($this->_['.'][$key]); | |
| } | |
| $this->_['.'][$key] = $val; | |
| self::_rConfig($this, $key); | |
| return $prev_val ? $prev_val : $this; | |
| } | |
| public function keyStat($key) { # Returns truthy if assignable, falsy if not. | |
| # null means unset, a boolean means it's a node object. A string means | |
| # it's some other type. Empty strings, false and null are all assignable. | |
| if ( array_key_exists($key, $this->_['.']) ) { | |
| if ($this->_['~']!==false) { | |
| if (is_a($this->_['.'][$key], get_class())) return true; | |
| elseif ($this->_['~']==='nodes') return false; | |
| elseif ($this->_['~']===true) return gettype($this->_['.'][$key]); | |
| } | |
| elseif (is_a($this->_['.'][$key], get_class())) return false; | |
| else return ''; | |
| } else return null; | |
| } | |
| public function echoNode($echo=true) { | |
| ini_set('xdebug.var_display_max_depth', 100); | |
| ini_set('xdebug.var_display_max_children', 256); | |
| ini_set('xdebug.var_display_max_data', 1024); | |
| $node_ob = clone $this; | |
| $recurse = function( &$node ) use (&$recurse) | |
| { | |
| if ( !empty($node->_['.']) ) | |
| foreach ($node->_['.'] as $k => $v) | |
| if ( is_a($v, get_class()) ) $recurse($v); | |
| $node->_['..'] = $node->_['..']->_['$']; | |
| $node->_['/'] = $node->_['/']->_['$']; | |
| return $node; | |
| }; | |
| $result = $recurse($node_ob); | |
| ob_start(); var_dump($result); | |
| $str = preg_replace('/\s+(=>)\s+/s','=>',ob_get_clean()); | |
| $str = str_replace([ "class "],'',$str); | |
| $str = preg_replace(['/protected/', '/#[0-9]+ \([0-9]+\)/', '/\n\s*}/', | |
| '/string\([0-9]+\)\s/','/array\([0-9]+\)\s/', '/object\((Node)\) {/', | |
| '/bool\(([a-zA-Z]+)\)/', '/int\(([0-9]+)\)/','/\n\s*(\s.\..=>)/',],'$1',$str); | |
| $str = preg_replace(['/\n\s*\$_=>\s*/s','/\n\s*(.#.=>)/s','/\n\s*(.\.\..=>)/s', | |
| '/\s+(.\!.=>)/s','/\s+(.\~.=>)/s','/\n\s*(.\/.=>)/s', | |
| '/{/', '/\s*(.\$.=>)/'],' $1',$str); // cut \n | |
| if ($echo) echo $str; | |
| return $str; | |
| } | |
| protected static function _rConfig(&$parent, $key=null) { | |
| # If $key is not provided, all contents of $parent will be configured recursively. | |
| # $key can specify a single key on $parent to recursively configure | |
| $next_ = ['#'=>$parent->_['#']+1,'!'=>$parent->_['!'],'..'=>$parent,'/'=>$parent->_['/']]; | |
| foreach ($parent->_['.'] as $k => &$v) { | |
| if ($key===null || $k===$key) { | |
| $next_['$'] = $k; | |
| if ( is_array($v) && $parent->_['~']) { | |
| $next_['~'] = $parent->_['~']===true ? true : false; | |
| $next_['.'] = $v; | |
| $v = new static(); | |
| $v->_ = array_merge($v->_, $next_); | |
| if (!empty($v->_['.'])) self::_rConfig($v); | |
| } elseif ( is_a($v, get_class()) ) { | |
| $next_['~'] = $v->_['~']; | |
| # unset from old parent if we need to: | |
| if ($v!==$v->_['..'] && isset($v->_['..']['.'][ $v->_['$'] ]) | |
| && $v->_['..']['.'][ $v->_['$'] ]===$v) | |
| unset($v->_['..']['.'][ $v->_['$'] ]); | |
| $v->_ = array_merge($v->_, $next_); | |
| if (!empty($v->_['.'])) self::_rConfig($v); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $node = new Node([ | |
| '1-leaf' => 'stringvalue', | |
| '1-tree1' => | |
| [ | |
| '2-tree' => | |
| [ | |
| '3-leaf' => 'stringvalue', | |
| '3-tree' => | |
| [ | |
| ['4-leaf' => 'stringvalue'], | |
| ] | |
| ] | |
| ], | |
| '1-tree2' => ['2-leaf' => 'stringvalue'], | |
| ]); | |
| echo gettype($node['1-tree1']['2-tree']['3-tree'])."\n"; | |
This file contains hidden or 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 NodeBase | |
| { | |
| protected $_node; | |
| public function __construct($array=[], $key_or_arr=null, $convert=null) | |
| { | |
| is_array($key_or_arr) ? extract($key_or_arr) : $key = $key_or_arr; | |
| $this->_node = [ | |
| 'key' => isset($key) ? $key : 'root', | |
| 'depth' => 0, | |
| 'convert' => isset($convert) ? $convert : true, | |
| 'arrays' => isset($arrays) ? $arrays : [], | |
| 'parent'=>$this, 'root'=>$this, 'children'=> $array | |
| ]; | |
| if ( !empty($this->_node['children']) ) self::_recurseNodes($this); | |
| } | |
| public function addNode ($data, $key_or_arr=null, $convert=null) | |
| { | |
| is_array($key_or_arr) ? extract($key_or_arr) : $key = $key_or_arr; | |
| $n =& $this->_node; | |
| if ( $key!==null && | |
| array_key_exists($key,$n['children']) ) return false; #overwriteERROR | |
| if (is_array($data) && $convert!==null || $n['convert']===true) { | |
| if($key===null){$key=0;while(array_key_exists($key,$n['children']))$key++;} | |
| $unempty = !empty($data); | |
| $node = new static(); | |
| $node->_node = $this->_next_node([ | |
| 'key'=>$key, 'convert'=>$convert,'children'=>$data | |
| ]); | |
| $n['children'][$key] = $node; | |
| if ($unempty) self::_recurseNodes($n['children'][$key]); | |
| } elseif ( is_a($data, get_class()) ) { | |
| if ($key===null) {$key = $data->_node['key']; # try passed node ob's key | |
| if (array_key_exists($key, $n['children'])) return false; #overwriteERROR | |
| } | |
| if ( $n['parent']!==$this ) $this->_unsetFromParent(); | |
| $data->_node = $this->_next_node(['key'=>$key, | |
| 'convert'=>$convert, 'children'=>$data->_node['children'],]); | |
| // if (in_array($key, $n['arrays']), unset($n['arrays'][$key])); | |
| $n['children'][$key] = $data; | |
| if ( !empty($n['children']) ) self::_recurseNodes($this); | |
| } else { | |
| if($key===null){$key=0;while(array_key_exists($key,$n['children']))$key++;} | |
| // if (is_array($data)) $n['arrays'][] = $key; | |
| $n['children'][$key] = $data; # some other type | |
| } | |
| return $this; | |
| } | |
| public function unsetNode () | |
| { | |
| } | |
| public function editNode ($parent_or_arr=null, $new_key=null, $convert=null) { | |
| if ( is_array($parent_or_arr) ) extract($parent_or_arr); | |
| else $new_parent=$parent_or_arr; | |
| $n =& $this->_node; $run_recurse = false; | |
| if ($new_parent===null) { | |
| #set key mode: | |
| if ($new_key!==null) { | |
| $arr =& $n['children']; $old_key = $n['key']; | |
| $valid_old = isset($arr[$old_key]) && $arr[$old_key]===$this; | |
| if ( $n['parent']===$this ) $n['key'] = $new_key; #no unset | |
| elseif ( array_key_exists($new_key,$arr) ) { #possible ERROR | |
| $real_key = array_search($this, $arr, true); | |
| if ($real_key!==$old_key) { | |
| if ($valid_old) unset($arr[$real_key]); | |
| else $n['key'] = $real_key; | |
| return false; | |
| } | |
| } else { | |
| $n['children'][$new_key] = $this; | |
| if ($valid_old) unset($n['children'][$old_key]);#must unset | |
| $n['key'] = $new_key; | |
| } | |
| } | |
| }#make root mode: | |
| elseif ($new_parent===$this && $n['parent']!==$this) { | |
| $this->_unsetFromParent(); | |
| $n['parent'] = $n['root'] = $this; | |
| if ($new_key!==null) $n['key'] = $new_key; | |
| if ( !empty($n['children']) ) $run_recurse = true; | |
| }#set parent mode: | |
| elseif ($new_parent!==$this && is_a($n['parent'], get_class()) ) { | |
| $n['parent']->addNode($this, $new_key, $convert); #addNode will recurse | |
| } | |
| if ($run_recurse || $convert!==null) self::_recurseNodes($this); | |
| return $this; | |
| } | |
| public function echoNode($echo=true) | |
| { | |
| $node_ob = clone $this; | |
| $recurse = function( &$node ) use (&$recurse) | |
| { | |
| if ( !empty($node->_node['children']) ) | |
| foreach ($node->_node['children'] as $k => $v) | |
| if ( is_a($v, get_class()) ) $recurse($v); | |
| $node->_node['parent'] = $node->_node['parent']->_node['key']; | |
| $node->_node['root'] = $node->_node['root']->_node['key']; | |
| return $node; | |
| }; | |
| $result = $recurse($node_ob); | |
| ob_start(); var_dump($result); | |
| $str = preg_replace('/(=>)\s+/s','=>',ob_get_clean()); | |
| $str = str_replace([ '"','[',']',],'',$str); | |
| $str = preg_replace(['/:protected/', '/#[0-9]+ \([0-9]+\)/', '/\n\s*}/', | |
| '/string\([0-9]+\)\s/','/array\([0-9]+\)\s/', '/object\((Node)\) {/', | |
| '/bool\(([a-zA-Z]+)\)/', '/int\(([0-9]+)\)/','/\n\s*children=>({)/',],'$1',$str); | |
| $str = preg_replace(['/\n\s*(depth=>)/s','/\n\s*(parent=>)/s', '/\n\s*_node=>{()\n\s*/s', | |
| '/\s{5}(convert=>)/s','/\n\s*(root=>)/s','/{/', '/\s*(key=>)/'],' $1',$str); | |
| if ($echo) echo $str; | |
| return $str; | |
| } | |
| protected function _unsetFromParent() | |
| { # STILL RUNS ON ROOT NODES! | |
| if (isset($this->_node['parent']->_node['children'][$this->_node['key']]) | |
| && $this->_node['parent']->_node['children'][$this->_node['key']]===$this | |
| ) { unset($this->_node['parent']->_node['children'][$this->_node['key']]);} | |
| } | |
| protected function _next_node($_node=[]) | |
| { # any keys passed in with value of null will not be used! | |
| return array_merge( [ | |
| 'key' => null, | |
| 'depth' => $this->_node['depth'] + 1, | |
| 'convert' => $this->_node['convert'], | |
| 'parent' => $this, | |
| 'root' => $this->_node['root'], | |
| 'children'=> [] ], | |
| array_filter($_node,function($v){return($v)!==null;})); | |
| } | |
| protected static function _recurseNodes(&$parent) | |
| { | |
| $next_node = [ | |
| 'key' => null, # just so it's at the beginning | |
| 'depth' => $parent->_node['depth'] + 1, | |
| 'convert'=> $parent->_node['convert'], | |
| 'parent' => $parent, | |
| 'root' => $parent->_node['root'],]; | |
| foreach ($parent->_node['children'] as $k => &$v) { | |
| $next_node['key'] = $k; | |
| if ($parent->_node['convert'] && is_array($v)) { | |
| $next_node['children'] = $v; | |
| $v = new static(); | |
| $v->_node = $next_node; | |
| if (!empty($v->_node['children'])) {self::_recurseNodes($v);} | |
| } elseif ( is_a($v, get_class()) ) { | |
| if ($v->_node['parent']!==$v) { | |
| if (isset($v->_node['parent']->_node['children'][$v->_node['key']]) | |
| && $v->_node['parent']->_node['children'][$v->_node['key']]===$v | |
| ) { unset($v->_node['parent']->_node['children'][$v->_node['key']]);} | |
| } | |
| $next_node['children'] = $v->_node['children']; | |
| $v->_node = $next_node; | |
| if ( !empty($v->_node['children']) ){self::_recurseNodes($v);} | |
| } | |
| } | |
| } | |
| } | |
| class NodeTree extends NodeBase | |
| implements Serializable, IteratorAggregate, ArrayAccess, Countable | |
| { | |
| # public node obj methods { access $this->_node property } return type | |
| public function parent() { return $this->_node['parent']; } #reference | |
| public function root() { return $this->_node['root']; } #reference | |
| public function parentKey() { return $this->parent()->key(); } #key | |
| public function rootKey() { return $this->root()->key(); } #key | |
| public function getKey() { return $this->nodeKey(); } #key | |
| public function nextIdx() { $i=0; | |
| while(array_key_exists($i,$this->_node['children']))$i++; #integer | |
| return $i; } # returns lowest integer key not in use | |
| public function depth() { return $this->_node['depth']; } #integer | |
| public function isRoot(){return $this->_node['root']===$this;} #boolean | |
| public function getChild($key){ | |
| return $this->_node['children'][$key]; } #element | |
| public function getSibling($key){ | |
| return $this->_node['parent']->_node['children'][$key]; } #element | |
| public function rootChild($key){ | |
| return $this->_node['root']->_node['children'][$key]; } #element | |
| public function hasKey($key) { | |
| return array_key_exists($key, $this->_node['children']); #boolean | |
| } | |
| public function serialize(){ | |
| return serialize($this->_node['children']); # object to string | |
| } | |
| public function unserialize($serialized) { | |
| $this->_node['children'] = unserialize($serialized); | |
| } | |
| public function getIterator() { | |
| return new ArrayIterator( $this->_node['children'] ); | |
| } | |
| public function count() { return count( $this->_node['children'] ); } | |
| public function length(){ return $this->count(); } | |
| public function offsetSet($key, $value) { | |
| if ($key===null) { | |
| $this->_node['children'][] = $value; | |
| } else { | |
| $this->_node['children'][$key] = $value; | |
| } | |
| } | |
| public function offsetGet($key) { | |
| return array_key_exists( $key,$this->_node['children'] ) | |
| ? $this->_node['children'][$key] : null; | |
| } | |
| public function offsetExists($key) { | |
| return array_key_exists( $key, $this->_node['children'] ); | |
| } | |
| public function offsetUnset($key) { | |
| unset( $this->_node['children'][$key] ); | |
| } | |
| } | |
| $node = new NodeTree([ | |
| '1-leaf' => 'stringvalue', | |
| '1-tree1' => | |
| [ | |
| '2-tree' => | |
| [ | |
| '3-leaf' => 'stringvalue', | |
| '3-tree' => | |
| [ | |
| ['4-leaf' => 'stringvalue'], | |
| ] | |
| ] | |
| ], | |
| '1-tree2' => ['2-leaf' => 'stringvalue'], | |
| ]); | |
| $node->addNode(['new'=>'child'], 'newnode'); | |
| // $node->nodeKey('ROOT'); | |
| $node->echoNode('echo'); | |
| $arr1 = new NodeTree([1, 2, 3]); | |
| $arr1["four"] = 4; | |
| foreach ($arr1 as $k => $v) echo "$k=>$v\n"; | |
| $arr2 = ["i'm", "an", "array?"]; | |
| $arr2 = new NodeTree($arr2); | |
| $arr2[] = 4; | |
| foreach ($arr2 as $k => $v) echo "$k=>$v\n"; | |
| # OLD OLD OLD OLD | |
| <?php | |
| class NodeBase | |
| { | |
| protected $_node; | |
| public function __construct($array=[], $_node=[]) | |
| { | |
| $this->_node = array_merge([ | |
| 'key' =>'root', 'depth'=> 0, 'convert' => true, | |
| 'parent'=> null, 'root' =>null, 'children'=> $array, | |
| ], $_node); | |
| if ( !isset($this->_node['parent']) ) { | |
| $this->_node['parent'] = $this; | |
| $this->_node['root'] = $this; | |
| } elseif ($this->_node['parent']!==$this) { | |
| if (isset($this->_node['parent']->_node['children'][$this->_node['key']]) | |
| && $this->_node['parent']->_node['children'][$this->_node['key']]===$this | |
| ) { unset($this->_node['parent']->_node['children'][$this->_node['key']]);} | |
| } | |
| if ( !empty($this->_node['children']) ) self::_recurseNodes($this); | |
| } | |
| public function nodeToString($echo=null) | |
| { | |
| $node_ob = clone $this; | |
| $recurse = function( &$node ) use (&$recurse) | |
| { | |
| if ( !empty($node->_node['children']) ) | |
| foreach ($node->_node['children'] as $k => $v) | |
| if ( is_a($v, get_class()) ) $recurse($v); | |
| $node->_node['parent'] = $node->_node['parent']->_node['key']; | |
| $node->_node['root'] = $node->_node['root']->_node['key']; | |
| return $node; | |
| }; | |
| $result = $recurse($node_ob); | |
| ob_start(); var_dump($result); | |
| $str = preg_replace('/(=>)\s+/s','=>',ob_get_clean()); | |
| $str = str_replace([ '"','[',']',],'',$str); | |
| $str = preg_replace(['/:protected/', '/#[0-9]+ \([0-9]+\)/', '/\n\s*}/', | |
| '/string\([0-9]+\)\s/','/array\([0-9]+\)\s/', '/object\((Node)\) {/', | |
| '/bool\(([a-zA-Z]+)\)/', '/int\(([0-9]+)\)/','/\n\s*children=>({)/',],'$1',$str); | |
| $str = preg_replace(['/\n\s*(depth=>)/s','/\n\s*(parent=>)/s', '/\n\s*_node=>{()\n\s*/s', | |
| '/\s{5}(convert=>)/s','/\n\s*(root=>)/s','/{/', '/\s*(key=>)/'],' $1',$str); | |
| if (!empty($echo)) echo $str; | |
| return $str; | |
| } | |
| public function nodeKey($new_key=null) | |
| { | |
| if ( $new_key===null ){return $this->_node['key'];} # getter mode | |
| $arr =& $this->_node['children']; $old_key = $this->_node['key']; | |
| $valid_old = isset($arr[$old_key]) && $arr[$old_key]===$this; | |
| if ( $this->_node['parent']===$this ) { | |
| $this->_node['key'] = $new_key; return $this; #DONE | |
| } elseif ( array_key_exists($new_key,$arr) ) { #something's wrong | |
| $real_key = array_search($this, $arr, true); | |
| if ($new_key===$real_key && $real_key===$old_key){return $this;} | |
| elseif ($real_key!==$old_key ){ | |
| if ($valid_old) {unset($arr[$real_key]);} | |
| else {$this->_node['key'] = $real_key;} | |
| return false; | |
| } | |
| } else { | |
| $this->_node['children'][$new_key] =& $this; | |
| if ($valid_old) unset($this->_node['children'][$old_key]); | |
| $this->_node['key'] = $new_key; return $this; | |
| } | |
| } | |
| public function rmFromParent($new_key=null) { | |
| if ( $this->_node['parent']!==$this ) { | |
| $this->_unsetFromParent(); | |
| $this->_node['parent'] = $this->_node['root'] = $this; | |
| if ($new_key!==null) $this->_node['key'] = $new_key; | |
| if ( !empty($this->_node['children']) ) self::_recurseNodes($this); | |
| } | |
| } | |
| public function addToParent($parent, $new_key=null) { | |
| if ($parent!==$this && is_a($parent, get_class()) ) | |
| $parent->addChild($this, $new_key); | |
| } | |
| public function addChild ($data, $key=null, $convert=null) | |
| { | |
| $children =& $this->_node['children']; | |
| if ($key!==null && array_key_exists($key,$children) )return false; #overwriteERROR | |
| if (is_array($data) && $convert!==null || $this->_node['convert']===true) { | |
| if($key===null){$key=0;while(array_key_exists($key,$children))$key++;} | |
| $unempty = !empty($data); | |
| $node = new static(); | |
| $node->_node = $this->_next_node(['key'=>$key, | |
| 'convert'=>$convert, 'children'=>$data]); | |
| $this->_node['children'][$key] = $node; | |
| if ($unempty) self::_recurseNodes($this->_node['children'][$key]); | |
| } elseif ( is_a($data, get_class()) ) { | |
| if ($key===null) {$key = $data->_node['key']; # try passed node ob's key | |
| if (array_key_exists($key, $children)) return false; #overwriteERROR | |
| } | |
| if ( $this->_node['parent']!==$this ) $this->_unsetFromParent(); | |
| $data->_node = $this->_next_node(['key'=>$key, | |
| 'convert'=>$convert, 'children'=>$data->_node['children'],]); | |
| $children[$key] = $data; | |
| if ( !empty($this->_node['children']) ) self::_recurseNodes($this); | |
| } else { | |
| if($key===null){$key=0;while(array_key_exists($key,$children))$key++;} | |
| $children[$key] = $data; # some other type | |
| } | |
| return $this; | |
| } | |
| protected static function _recurseNodes(&$parent) | |
| { | |
| $next_node = [ | |
| 'key' => null, # just so it's at the beginning | |
| 'depth' => $parent->_node['depth'] + 1, | |
| 'convert'=> $parent->_node['convert'], | |
| 'parent' => $parent, | |
| 'root' => $parent->_node['root'],]; | |
| foreach ($parent->_node['children'] as $k => &$v) { | |
| $next_node['key'] = $k; | |
| if ($parent->_node['convert'] && is_array($v)) { | |
| $next_node['children'] = $v; | |
| $v = new static(); | |
| $v->_node = $next_node; | |
| if (!empty($v->_node['children'])) {self::_recurseNodes($v);} | |
| } elseif ( is_a($v, get_class()) ) { | |
| if ($v->_node['parent']!==$v) { | |
| if (isset($v->_node['parent']->_node['children'][$v->_node['key']]) | |
| && $v->_node['parent']->_node['children'][$v->_node['key']]===$v | |
| ) { unset($v->_node['parent']->_node['children'][$v->_node['key']]);} | |
| } | |
| $next_node['children'] = $v->_node['children']; | |
| $v->_node = $next_node; | |
| if ( !empty($v->_node['children']) ){self::_recurseNodes($v);} | |
| } | |
| } | |
| } | |
| protected function _unsetFromParent() { # STILL RUNS ON ROOT NODES! | |
| if (isset($this->_node['parent']->_node['children'][$this->_node['key']]) | |
| && $this->_node['parent']->_node['children'][$this->_node['key']]===$this | |
| ) { unset($this->_node['parent']->_node['children'][$this->_node['key']]);} | |
| } | |
| protected function _next_node($_node=[]) | |
| { # any keys passed in with value of null will not be used! | |
| return array_merge( [ | |
| 'key' => null, | |
| 'depth' => $this->_node['depth'] + 1, | |
| 'convert' => $this->_node['convert'], | |
| 'parent' => $this, | |
| 'root' => $this->_node['root'], | |
| 'children'=> [] ], | |
| array_filter($_node,function($v){return($v)!==null;})); | |
| } | |
| } | |
| class NodeTree extends NodeBase | |
| implements Serializable, IteratorAggregate, ArrayAccess, Countable | |
| { | |
| # public node obj methods { access $this->_node property } return type | |
| public function parent() { return $this->_node['parent']; } #reference | |
| public function root() { return $this->_node['root']; } #reference | |
| public function parentKey() { return $this->parent()->key(); } #key | |
| public function rootKey() { return $this->root()->key(); } #key | |
| public function getKey() { return $this->nodeKey(); } #key | |
| public function setKey($key){ return $this->nodeKey($key);} #this or false | |
| public function nextIdx() { $i=0; | |
| while(array_key_exists($i,$this->_node['children']))$i++; #integer | |
| return $i; } # returns lowest integer key not in use | |
| public function depth() { return $this->_node['depth']; } #integer | |
| public function isRoot(){return $this->_node['root']===$this;} #boolean | |
| public function getChild($key){ | |
| return $this->_node['children'][$key]; } #element | |
| public function getSibling($key){ | |
| return $this->_node['parent']->_node['children'][$key]; } #element | |
| public function rootChild($key){ | |
| return $this->_node['root']->_node['children'][$key]; } #element | |
| public function hasKey($key) { | |
| return array_key_exists($key, $this->_node['children']); #boolean | |
| } | |
| public function serialize(){ | |
| return serialize($this->_node['children']); # object to string | |
| } | |
| public function unserialize($serialized) { | |
| $this->_node['children'] = unserialize($serialized); | |
| } | |
| public function getIterator() { | |
| return new ArrayIterator( $this->_node['children'] ); | |
| } | |
| public function count() { return count( $this->_node['children'] ); } | |
| public function length(){ return $this->count(); } | |
| public function offsetSet($key, $value) { | |
| if ($key===null) { | |
| $this->_node['children'][] = $value; | |
| } else { | |
| $this->_node['children'][$key] = $value; | |
| } | |
| } | |
| public function offsetGet($key) { | |
| return array_key_exists( $key,$this->_node['children'] ) | |
| ? $this->_node['children'][$key] : null; | |
| } | |
| public function offsetExists($key) { | |
| return array_key_exists( $key, $this->_node['children'] ); | |
| } | |
| public function offsetUnset($key) { | |
| unset( $this->_node['children'][$key] ); | |
| } | |
| } | |
| $node = new NodeTree([ | |
| '1-leaf' => 'stringvalue', | |
| '1-tree1' => | |
| [ | |
| '2-tree' => | |
| [ | |
| '3-leaf' => 'stringvalue', | |
| '3-tree' => | |
| [ | |
| ['4-leaf' => 'stringvalue'], | |
| ] | |
| ] | |
| ], | |
| '1-tree2' => ['2-leaf' => 'stringvalue'], | |
| ]); | |
| // var_dump($node); | |
| $node->addChild(['new'=>'child'], 'newnode'); | |
| $node->nodeKey('ROOT'); | |
| $node->nodeToString('echo'); | |
| $arr1 = new NodeTree([1, 2, 3]); | |
| $arr1["four"] = 4; | |
| foreach ($arr1 as $k => $v) echo "$k=>$v\n"; | |
| $arr2 = ["i'm", "an", "array?"]; | |
| $arr2 = new NodeTree($arr2); | |
| $arr2[] = 4; | |
| foreach ($arr2 as $k => $v) echo "$k=>$v\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment