Traverser::traverse()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * For licensing information, please see the LICENSE file accompanied with this file.
4
 *
5
 * @author Gerard van Helden <[email protected]>
6
 * @copyright 2012 Gerard van Helden <http://melp.nl>
7
 */
8
namespace Zicht\Tool\Container;
9
10
/**
11
 * A tree traverser which can be used to alter a nested array tree structure
12
 */
13
class Traverser
14
{
15
    /**
16
     * If this is used as the visitor type, the visitor is called right BEFORE entering the child node list
17
     * of the node
18
     */
19
    const BEFORE = 1;
20
21
    /**
22
     * If this is used as the visitor type, the visitor is called right AFTER the children have been visited.
23
     */
24
    const AFTER = 2;
25
26
    /**
27
     * The tree to traverse
28
     *
29
     * @var array
30
     */
31
    protected $tree;
32
33
    /**
34
     * The visitors to apply to the tree
35
     * @var array
36
     */
37
    protected $visitors;
38
39
40
    /**
41
     * Construct the traverser with the specified tree as input.
42
     *
43
     * @param array $tree
44
     */
45
    public function __construct($tree)
46
    {
47
        $this->tree = $tree;
48
        $this->visitors = array();
49
    }
50
51
52
    /**
53
     * Add a visitor to the traverser. The node being visited is passed to the second callback to determine whether
54
     * the first callback should be called with the node. The arguments passed to both callbacks are the current node
55
     * and the path to the node. The result of the first callback is used to replace the node in the tree.
56
     *
57
     * Example:
58
     * <code>
59
     * $traverser->addVisitor(
60
     *     function($path, $node) {
61
     *         $node['i was visited'] = true;
62
     *         return $node;
63
     *     },
64
     *     function($path, $node) {
65
     *         return count($path) == 3 && $path[0] == 'foo' && $path[2] == 'bar';
66
     *     }
67
     * );
68
     *
69
     * $traverser->traverse(
70
     *     array(
71
     *         'foo' => array(
72
     *             array(
73
     *                 'bar' => array('i should be visited' => true),
74
     *                 'baz' => array('i should not be visited' => true)
75
     *             )
76
     *         )
77
     *     )
78
     * );
79
     * </code>
80
     *
81
     * @param callable $callable
82
     * @param callable $condition
83
     * @param int $when
84
     * @return self
85
     */
86
    public function addVisitor($callable, $condition, $when = self::BEFORE)
87
    {
88
        $this->visitors[] = array($when, $condition, $callable);
89
90
        return $this;
91
    }
92
93
94
    /**
95
     * Traverse the entire tree
96
     *
97
     * @return mixed
98
     */
99
    public function traverse()
100
    {
101
        return $this->doTraverse($this->tree);
102
    }
103
104
105
    /**
106
     * Recursive traversal implementation
107
     *
108
     * @param mixed $node
109
     * @param array $path
110
     * @return mixed
111
     */
112
    private function doTraverse($node, $path = array())
113
    {
114
        foreach ($node as $name => $value) {
115
            $path[] = $name;
116
            $value = $this->doVisit($path, $value, self::BEFORE);
117
118
            if (is_array($value)) {
119
                $value = $this->doTraverse($value, $path);
120
            }
121
122
            $value = $this->doVisit($path, $value, self::AFTER);
123
            $node[$name] = $value;
124
            array_pop($path);
125
        }
126
127
        return $node;
128
    }
129
130
131
    /**
132
     * Visits the node with all visitors at the specified time.
133
     *
134
     * @param array $path
135
     * @param mixed $value
136
     * @param int $when
137
     * @return mixed
138
     *
139
     * @throws \RuntimeException
140
     */
141
    private function doVisit($path, $value, $when)
142
    {
143
        foreach ($this->visitors as $visitor) {
144
            if ($visitor[0] === $when && call_user_func($visitor[1], $path, $value)) {
145
                try {
146
                    $value = call_user_func($visitor[2], $path, $value);
147
                } catch (\Exception $e) {
148
                    if ($path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
149
                        $path = join('.', $path);
150
                    }
151
                    $path = json_encode($path);
152
                    $value = json_encode($value);
153
                    throw new \RuntimeException("While visiting value '{$value}' at path {$path}", 0, $e);
154
                }
155
            }
156
        }
157
        return $value;
158
    }
159
}