Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
17 | class tree |
||
18 | { |
||
19 | /** |
||
20 | * Create a simple array from a tree. Each non-null nodeValue will be added with the path to its node as the key |
||
21 | * @param \arc\tree\Node $node |
||
22 | * @param string $root |
||
23 | * @param string $nodeName |
||
24 | * @return array [ $path => $data, ... ] |
||
25 | */ |
||
26 | 10 | public static function collapse($node, $root = '', $nodeName = 'nodeName') |
|
27 | { |
||
28 | 10 | return \arc\tree::map( |
|
29 | 10 | $node, |
|
30 | 10 | function ($child) { |
|
31 | 10 | return $child->nodeValue; |
|
32 | 10 | }, |
|
33 | $root, |
||
34 | $nodeName |
||
35 | ); |
||
36 | } |
||
37 | |||
38 | /** |
||
39 | * Creates a NamedNode tree from an array with path => nodeValue entries. |
||
40 | * @param array $tree The collapsed tree: [ $path => $data, ... ] |
||
41 | * @return \arc\tree\NamedNode an object tree with parent/children relations |
||
42 | */ |
||
43 | 16 | public static function expand($tree = null) |
|
44 | { |
||
45 | 16 | if (is_object( $tree ) && isset( $tree->childNodes )) { |
|
46 | return $tree; //FIXME: should we clone the tree to avoid shared state? |
||
47 | } |
||
48 | 16 | $root = new \arc\tree\NamedNode(); |
|
49 | 16 | if (!is_array($tree)) { |
|
50 | 12 | return $root; // empty tree |
|
51 | } |
||
52 | 4 | $previousParent = $root; |
|
53 | 4 | foreach ($tree as $path => $data) { |
|
54 | 4 | $previousPath = $previousParent->getPath(); |
|
55 | 4 | $subPath = \arc\path::diff( $previousPath, $path ); |
|
56 | 4 | if ($subPath) { |
|
57 | // create missing parent nodes, input tree may be sparsely filled |
||
58 | 4 | $node = \arc\path::reduce( |
|
59 | 4 | $subPath, |
|
60 | 4 | function ($previous, $name) { |
|
61 | 4 | if ($name == '..') { |
|
62 | 4 | return $previous->parentNode; |
|
63 | } |
||
64 | |||
65 | 4 | return $previous->appendChild( $name ); |
|
66 | 4 | }, |
|
67 | 4 | $previousParent |
|
68 | ); |
||
69 | } else { |
||
70 | // means the previousParent is equal to the current path, e.g. the root |
||
71 | $node = $previousParent; |
||
72 | } |
||
73 | 4 | $node->nodeValue = $data; |
|
74 | 4 | $previousParent = $node; |
|
75 | } |
||
76 | |||
77 | 4 | return $root; |
|
78 | } |
||
79 | |||
80 | /** |
||
81 | * Calls the first callback method on each successive parent until a non-null value is returned. Then |
||
82 | * calls all the parents from that point back to this node with the second callback in reverse order. |
||
83 | * The first callback (dive) must accept one parameter, the node. |
||
84 | * The second callback (rise) must accept two parameters, the nde and the result up to that point. |
||
85 | * @param \arc\tree\Node $node A tree node, must have traversable childNodes property and a parentNode property |
||
86 | * @param callable $diveCallback The callback for the dive phase. |
||
87 | * @param callable $riseCallback The callback for the rise phase. |
||
88 | * @return mixed |
||
89 | */ |
||
90 | 8 | public static function dive($node, $diveCallback = null, $riseCallback = null) |
|
91 | { |
||
92 | 8 | $result = null; |
|
93 | 8 | if (is_callable( $diveCallback )) { |
|
94 | 6 | $result = call_user_func( $diveCallback, $node ); |
|
95 | } |
||
96 | 8 | if (!isset( $result ) && $node->parentNode) { |
|
97 | 4 | $result = \arc\tree::dive( $node->parentNode, $diveCallback, $riseCallback ); |
|
|
|||
98 | } |
||
99 | 8 | if (is_callable( $riseCallback )) { |
|
100 | 4 | return call_user_func( $riseCallback, $node, $result ); |
|
101 | } else { |
||
102 | 6 | return $result; |
|
103 | } |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Calls the callback method on each parent of the given node, starting at the root. |
||
108 | * @param \arc\tree\Node $node A tree node, must have traversable childNodes property and a parentNode property |
||
109 | * @param callable $callback The callback function applied to each parent. |
||
110 | * @return mixed |
||
111 | */ |
||
112 | 4 | public static function parents($node, $callback = null) |
|
122 | |||
123 | /** |
||
124 | * Calls the callback method on each of the direct child nodes of the given node. |
||
125 | * @param \arc\tree\Node $node |
||
126 | * @param callable $callback The callback function applied to each child node |
||
127 | * @param mixed $nodeName The name of the 'name' property or a function that returns the name of a node. |
||
128 | * @return array |
||
129 | */ |
||
130 | View Code Duplication | public static function ls($node, $callback, $nodeName = 'nodeName') |
|
131 | { |
||
132 | $result = []; |
||
133 | foreach ($node->childNodes as $child) { |
||
134 | $name = self::getNodeName( $child, $nodeName ); |
||
135 | $result[ $name ] = call_user_func( $callback, $child ); |
||
136 | } |
||
137 | |||
138 | return $result; |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Calls the callback method on each child of the current node, including the node itself, until a non-null |
||
143 | * result is returned. Returns that result. The tree is searched depth first. |
||
144 | * @param \arc\tree\Node $node |
||
145 | * @param callable $callback The callback function applied to each child node |
||
146 | * @return mixed |
||
147 | */ |
||
148 | public static function search($node, $callback) |
||
149 | { |
||
150 | $result = call_user_func( $callback, $node ); |
||
151 | if (isset( $result )) { |
||
152 | return $result; |
||
153 | } |
||
154 | foreach ($node->childNodes as $child) { |
||
155 | $result = self::search( $child, $callback ); |
||
156 | if (isset( $result )) { |
||
157 | return $result; |
||
158 | } |
||
159 | } |
||
160 | |||
161 | return null; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Calls the callback method on each child of the current node, including the node itself. Any non-null result |
||
166 | * is added to the result array, with the path to the node as the key. |
||
167 | * @param \arc\tree\Node $node |
||
168 | * @param callable $callback The callback function applied to each child node |
||
169 | * @param string $root |
||
170 | * @param mixed $nodeName The name of the 'name' property or a function that returns the name of a node. |
||
171 | * @return array |
||
172 | */ |
||
173 | 10 | public static function map($node, $callback, $root = '', $nodeName = 'nodeName') |
|
174 | { |
||
175 | 10 | $result = []; |
|
176 | 10 | $name = self::getNodeName( $node, $nodeName ); |
|
177 | 10 | $path = $root . $name . '/'; |
|
178 | 10 | $callbackResult = call_user_func( $callback, $node ); |
|
179 | 10 | if (isset($callbackResult)) { |
|
180 | 10 | $result[ $path ] = $callbackResult; |
|
181 | } |
||
182 | 10 | foreach ($node->childNodes as $child) { |
|
183 | 10 | $result += self::map( $child, $callback, $path, $nodeName ); |
|
184 | } |
||
185 | |||
186 | 10 | return $result; |
|
187 | } |
||
188 | |||
189 | /** |
||
190 | * Calls the callback method on all child nodes of the given node, including the node itself. The result of each |
||
191 | * call is passed on as the first argument to each succesive call. |
||
192 | * @param \arc\tree\Node $node |
||
193 | * @param callable $callback |
||
194 | * @param mixed $initial The value to pass to the first callback call. |
||
195 | * @return mixed |
||
196 | */ |
||
197 | View Code Duplication | public static function reduce($node, $callback, $initial = null) |
|
198 | { |
||
199 | $result = call_user_func( $callback, $initial, $node ); |
||
200 | foreach ($node->childNodes as $child) { |
||
201 | $result = self::reduce( $child, $callback, $result ); |
||
202 | } |
||
203 | |||
204 | return $result; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * Filters the tree using a callback method. If the callback method returns true, the node's value is included |
||
209 | * in the result, otherwise it is skipped. Filter returns a collapsed tree: [ path => nodeValue ] |
||
210 | * The callback method must take one argument: the current node. |
||
211 | * @param \arc\tree\Node $node |
||
212 | * @param callable $callback |
||
213 | * @return array |
||
214 | */ |
||
215 | public static function filter($node, $callback, $root = '', $nodeName = 'nodeName') |
||
216 | { |
||
217 | return self::map( $node, function ($node) use ($callback) { |
||
218 | if (call_user_func( $callback, $node )) { |
||
219 | return $node->nodeValue; |
||
220 | } |
||
221 | }, $root, $nodeName ); |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * Sorts the childNodes list of the node, recursively. |
||
226 | * @param \arc\tree\Node $node |
||
227 | * @param callable $callback |
||
228 | * @param mixed $nodeName |
||
229 | * @throws UnknownError |
||
230 | */ |
||
231 | public static function sort($node, $callback, $nodeName = 'nodeName') |
||
247 | |||
248 | 10 | private static function getNodeName($node, $nodeName) |
|
249 | { |
||
250 | 10 | if (is_callable($nodeName)) { |
|
251 | $name = call_user_func( $nodeName, $node ); |
||
252 | } else { |
||
253 | 10 | $name = $node->{$nodeName}; |
|
254 | } |
||
255 | |||
258 | } |
||
259 |
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.