Advertisement
rfv123

Q30299576/AllPaths - TreePaths class

Jan 23rd, 2016
929
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 6.40 KB | None | 0 0
  1. <?php // http://stackoverflow.com/questions/30299576/build-paths-of-all-paths-of-a-tree-structured-array-in-php
  2.  
  3. /*
  4.  * Tree Node:
  5.  *
  6.  * Array(
  7.  *        "name" => 'Furniture',  // not checked
  8.  *        "slug" => 'furniture',  // optional
  9.  *        "children" => Array( // can be other Tree nodes...
  10.  *                          ),
  11.  *      );
  12.  *
  13.  *  The `children` key is optional, if empty or missing, means it is a `leaf` node
  14.  *
  15.  *  !!! Note: The only array entry checked in here is 'children' !!!
  16.  *
  17.  *  But you will need to overide the default nodeProcessor.
  18.  *
  19.  *  The default `nodeProcessor` uses `name` and `children` only
  20.  */
  21.  
  22. /*
  23.  * NodeProcessor:
  24.  *   o It is a callable that accepts two parameters
  25.  *     o current path - an array of all the nodes so far in this path
  26.  *     o isTopALeaf   - is the end of the path a 'leaf' node?
  27.  */
  28.  
  29. /**
  30.  *  Traverse the tree of `nodes`
  31.  *  Generate a list of Paths from Root to every leaf node as an array of `nodes`.
  32.  *  It is a `stack` with the top node being a leaf.
  33.  */
  34. class TreePaths {
  35.  
  36.     /**
  37.      * The 'current' menu / tree
  38.      *
  39.      * @var array $tree
  40.      */
  41.     private $tree = array();
  42.  
  43.  
  44.     /**
  45.      * The Output
  46.      *
  47.      * @var array $allPaths
  48.      */
  49.     private $allPaths = array();
  50.  
  51.     /**
  52.      * The 'current' stack of nodes in this path
  53.      *
  54.      * This is a 'stack'. The 'path' is all the entries combined.
  55.      *
  56.      * @var array $currentPath
  57.      */
  58.     private $currentPath = array();
  59.  
  60.  
  61.     /**
  62.      * The 'callable' to be run for nodes
  63.      *
  64.      * @var callable $nodeProcessor
  65.      */
  66.     private $nodeProcessor = null;
  67.  
  68.  
  69.     /**
  70.      * Call All Nodes or Leaf node only
  71.      *
  72.      * @var boolean
  73.      */
  74.     private $callLeafNodesOnly = true;
  75.  
  76.  
  77.     /**
  78.      * Build the class but do not run it...
  79.      *
  80.      * Provides a default NodeProcessor if you don't provide one.
  81.      *    o The default processor builds string paths that look like filepaths
  82.      *
  83.      * @param array $tree
  84.      * @param callable $processNode        - optional
  85.      * @param boolean $callLeafNodesOnly  - optional default true
  86.      */
  87.     public function __construct(array $tree,
  88.                                    /* callable */ $processNode = null,
  89.                                    $callLeafNodesOnly = true)
  90.     {
  91.         $this->tree = $tree;
  92.         $this->nodeProcessor = $processNode;
  93.         $this->callLeafNodesOnly = $callLeafNodesOnly;
  94.  
  95.         // provide a default processor
  96.         if (is_null($this->nodeProcessor)) {
  97.             $this->nodeProcessor = $this->defaultNodeProcessor();
  98.         }
  99.     }
  100.  
  101.     /**
  102.      * This routine makes this class rather powerful as you can use any callable.
  103.      *
  104.      * @param type $nodeProcessor
  105.      */
  106.     public function setNodeProcessor(/* callable */ $nodeProcessor)
  107.     {
  108.         $this->nodeProcessor = $nodeProcessor;
  109.     }
  110.  
  111.     /**
  112.      * Return a list of all the paths that were generated by the 'nodeProcessor'
  113.      * @return array
  114.      */
  115.     public function allPaths()
  116.     {
  117.         return $this->allPaths;
  118.     }
  119.  
  120.     /**
  121.      * The routine that processes one node and recurses as required
  122.      *
  123.      * @param array $node
  124.      * @return void This is all side-effects
  125.      */
  126.     protected function treeWalk($node)
  127.     {
  128.         // always add the node to the currentPath
  129.         array_push($this->currentPath, $node);
  130.  
  131.         // Always call the node processor and add the path to all paths if required
  132.         $processedPath = $this->callNodeProcessor($this->currentPath,
  133.                                                   $this->isLeafNode($node));
  134.         if (!empty($processedPath)) { // add to all the paths
  135.             $this->allPaths[] = $processedPath;
  136.         }
  137.  
  138.         // do we recurse?
  139.         if ($this->isLeafNode($node)) { // no we dont...
  140.             array_pop($this->currentPath); // lose leaf node from top of stack
  141.             return; // nothing more to do
  142.         }
  143.  
  144.         // now process all the children... This will recurse - always
  145.         foreach ($node['children'] as $key => $node) {
  146.             $this->treeWalk($node);
  147.         }
  148.         return; // end of children
  149.     }
  150.  
  151.     /**
  152.      * Process all the top level nodes.
  153.      *
  154.      * @return void
  155.      */
  156.     public function generate()
  157.     {
  158.         $this->allPaths = array();
  159.  
  160.         foreach ($this->tree as $key => $node) {
  161.             $this->treeWalk($node);
  162.         }
  163.         return;
  164.     }
  165.  
  166.     /**
  167.      * End of a path?
  168.      *
  169.      * @param array $node
  170.      * @return boolean
  171.      */
  172.     protected function isLeafNode($node)
  173.     {
  174.         return empty($node['children']);
  175.     }
  176.  
  177.     /**
  178.      * Are we in the 'middle' of a path?
  179.      *
  180.      * @param array $node
  181.      * @return boolean
  182.      */
  183.     protected function hasChildren($node)
  184.     {
  185.         return !empty($node['children']);
  186.     }
  187.  
  188.  
  189.     /**
  190.      * The `callable` to be called.
  191.      *
  192.      * It must accept the two parameters.
  193.      *
  194.      * It can be set after the 'class instance' is created.
  195.      *
  196.      * @param array currentPath to this value
  197.      * @param string nodeType - leaf or children
  198.      *
  199.      * @return mixed if not empty will be added to the paths
  200.      */
  201.     protected function callNodeProcessor(array $currentPath,
  202.                                              $isTopALeaf)
  203.     {
  204.  
  205.         if ($this->callLeafNodesOnly) {
  206.             if ($isTopALeaf)  {
  207.                 return call_user_func($this->nodeProcessor,
  208.                                        $currentPath,
  209.                                        $isTopALeaf);
  210.             }
  211.         }
  212.         else {
  213.             return call_user_func($this->nodeProcessor,
  214.                                    $currentPath,
  215.                                    $isTopALeaf);
  216.         }
  217.     }
  218.  
  219.     /**
  220.      * If you don't provide a callable to generate paths then this will be used.
  221.      *
  222.      * It generates a string of names separated by '/'. i.e. it looks like a filepath.
  223.      *
  224.      * @return string
  225.      */
  226.     public function defaultNodeProcessor()
  227.     {
  228.         $dnp = function(array $currentPath,
  229.                          $isTopALeaf)  {
  230.  
  231.                 $fullPath = '/';
  232.                 foreach($currentPath as $key => $node) {
  233.                     $fullPath .= $node['name'] .'/';
  234.                 }
  235.                 return rtrim($fullPath, '/');
  236.              };
  237.          return $dnp;
  238.     }
  239. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement