This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * See class comment |
||
4 | * |
||
5 | * PHP Version 5 |
||
6 | * |
||
7 | * @category Netresearch |
||
8 | * @package Netresearch\Kite |
||
9 | * @author Christian Opitz <[email protected]> |
||
10 | * @license http://www.netresearch.de Netresearch Copyright |
||
11 | * @link http://www.netresearch.de |
||
12 | */ |
||
13 | |||
14 | namespace Netresearch\Kite; |
||
15 | |||
16 | use Netresearch\Kite\Exception; |
||
17 | use Netresearch\Kite\ExpressionLanguage\ExpressionLanguage; |
||
18 | |||
19 | /** |
||
20 | * Variable container class |
||
21 | * |
||
22 | * @category Netresearch |
||
23 | * @package Netresearch\Kite |
||
24 | * @author Christian Opitz <[email protected]> |
||
25 | * @license http://www.netresearch.de Netresearch Copyright |
||
26 | * @link http://www.netresearch.de |
||
27 | */ |
||
28 | class Variables implements \ArrayAccess |
||
29 | { |
||
30 | /** |
||
31 | * @var array |
||
32 | */ |
||
33 | private $variables = array(); |
||
34 | |||
35 | /** |
||
36 | * @var Variables |
||
37 | */ |
||
38 | private $parent; |
||
39 | |||
40 | /** |
||
41 | * @var Variables[] |
||
42 | */ |
||
43 | private $children = array(); |
||
44 | |||
45 | /** |
||
46 | * Variables constructor. |
||
47 | * |
||
48 | * @param Variables|null $parent Parent variables container |
||
49 | * @param array $defaults Array with default values |
||
50 | */ |
||
51 | public function __construct(Variables $parent = null, array $defaults = array()) |
||
52 | { |
||
53 | $this->parent = $parent === $this ? null : $parent; |
||
54 | if ($this->parent) { |
||
55 | $this->parent->children[] = $this; |
||
56 | } |
||
57 | $variableConfiguration = $this->configureVariables(); |
||
58 | foreach ($variableConfiguration as $variable => $config) { |
||
59 | if (is_array($config)) { |
||
60 | if (!array_key_exists($variable, $defaults) && (!array_key_exists('required', $config) || !$config['required'])) { |
||
61 | $defaults[$variable] = array_key_exists('default', $config) ? $config['default'] : null; |
||
62 | } |
||
63 | } elseif ($config === null) { |
||
64 | $defaults[$variable] = null; |
||
65 | } elseif (!is_numeric($variable) && $config !== false) { |
||
66 | throw new Exception('Invalid variable configuration'); |
||
67 | } |
||
68 | } |
||
69 | $this->variables += $defaults; |
||
70 | $this->variables['_variableConfiguration'] = $variableConfiguration; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * Clone the children with this step as well, cause otherwise they'll lose |
||
75 | * connection to parent |
||
76 | * |
||
77 | * @return void |
||
78 | */ |
||
79 | function __clone() |
||
0 ignored issues
–
show
|
|||
80 | { |
||
81 | $children = array(); |
||
82 | foreach ($this->children as $child) { |
||
83 | $children[] = $clone = clone $child; |
||
84 | $clone->parent = $this; |
||
85 | } |
||
86 | $this->children = $children; |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Bind to another parent |
||
91 | * |
||
92 | * @param Variables $newParent The new parent |
||
93 | * |
||
94 | * @return void |
||
95 | */ |
||
96 | public function bindTo(Variables $newParent) |
||
97 | { |
||
98 | if ($this->parent) { |
||
99 | foreach ($this->parent->children as $i => $child) { |
||
100 | if ($child === $this) { |
||
101 | unset($this->parent->children[$i]); |
||
102 | break; |
||
103 | } |
||
104 | } |
||
105 | } |
||
106 | $this->parent = $newParent; |
||
107 | $this->parent->children[] = $this; |
||
108 | } |
||
109 | |||
110 | /** |
||
111 | * Get the parent variables object, if any |
||
112 | * |
||
113 | * @return Variables |
||
114 | */ |
||
115 | public function getParent() |
||
116 | { |
||
117 | return $this->parent; |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Provide an array of variable configurations: |
||
122 | * - Keys are the variable names |
||
123 | * - Values can be |
||
124 | * -- an array with the configuration - following keys are interpreted: |
||
125 | * --- required (defaults to false) |
||
126 | * --- default (defaults to null, ignored when required) |
||
127 | * -- null: The default value will be set to null |
||
128 | * -- false: No default value will be set |
||
129 | * |
||
130 | * Also you can add a "--" with numeric key, to state the boundaries of |
||
131 | * current and parent variables configuration |
||
132 | * |
||
133 | * In either case variables in this configuration will always be fetched |
||
134 | * from and saved to the very scope of this variables object (this) |
||
135 | * |
||
136 | * @return array |
||
137 | */ |
||
138 | protected function configureVariables() |
||
139 | { |
||
140 | return array(); |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * Get a variable from this very object (unexpanded) |
||
145 | * |
||
146 | * @param mixed $offset Variable name |
||
147 | * |
||
148 | * @internal Required set/get/has/remove in order to access entries of Variables |
||
149 | * without looking up parents. |
||
150 | * You can however override this to do your own logic on plain offsets |
||
151 | * (no dot paths as in set/get/has/remove). |
||
152 | * |
||
153 | * @return mixed |
||
154 | */ |
||
155 | public function &offsetGet($offset) |
||
156 | { |
||
157 | return $this->variables[$offset]; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Determine if a variable is available on this very object |
||
162 | * |
||
163 | * @param mixed $offset Variable name |
||
164 | * |
||
165 | * @internal See {@see Variables::offsetGet()} |
||
166 | * |
||
167 | * @return boolean |
||
168 | */ |
||
169 | public function offsetExists($offset) |
||
170 | { |
||
171 | return array_key_exists($offset, $this->variables); |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * Set a variable on this very object |
||
176 | * |
||
177 | * @param mixed $offset Variable name |
||
178 | * @param mixed $value The value |
||
179 | * |
||
180 | * @internal See {@see Variables::offsetGet()} |
||
181 | * |
||
182 | * @return void |
||
183 | */ |
||
184 | public function offsetSet($offset, $value) |
||
185 | { |
||
186 | $this->variables[$offset] = $value; |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Unset a variable from this very object |
||
191 | * |
||
192 | * @param mixed $offset Variable name |
||
193 | * |
||
194 | * @internal See {@see Variables::offsetGet()} |
||
195 | * |
||
196 | * @return mixed |
||
197 | */ |
||
198 | public function offsetUnset($offset) |
||
199 | { |
||
200 | unset($this->variables[$offset]); |
||
201 | } |
||
202 | |||
203 | |||
204 | /** |
||
205 | * Find the first context that contains the first part of the variable |
||
206 | * (this will always return current context - configured variables as well) |
||
207 | * |
||
208 | * @param array $variableParts The path split by dot |
||
209 | * |
||
210 | * @return Variables |
||
211 | */ |
||
212 | private function findContext(&$variableParts) |
||
213 | { |
||
214 | if (!$variableParts) { |
||
0 ignored issues
–
show
The expression
$variableParts 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 ![]() |
|||
215 | return $this; |
||
216 | } |
||
217 | if (array_key_exists($variableParts[0], $this->variables['_variableConfiguration'])) { |
||
218 | // Configured variables always belong to the current scope |
||
219 | return $this; |
||
220 | } |
||
221 | if ($variableParts[0] === 'this') { |
||
222 | array_shift($variableParts); |
||
223 | } elseif ($variableParts[0] === 'parent') { |
||
224 | if (!$this->parent) { |
||
225 | throw new Exception('No parent object available'); |
||
226 | } |
||
227 | array_shift($variableParts); |
||
228 | return $this->parent; |
||
229 | } else { |
||
230 | // Look up this and all parents, if they have the variable |
||
231 | $parent = $this; |
||
232 | do { |
||
233 | if ($parent->offsetExists($variableParts[0])) { |
||
234 | return $parent; |
||
235 | } |
||
236 | } while ($parent = $parent->parent); |
||
237 | } |
||
238 | return $this; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Get an expanded variable by it's path (second argument can be a default value) |
||
243 | * |
||
244 | * @param string $name The variable name |
||
245 | * |
||
246 | * @final This method is not to be overwritten as logic is to complex to deal |
||
247 | * with overrides because callee detection might be introduced later on |
||
248 | * and the expansion logic of dot path's is mostly internal. |
||
249 | * If you need to intercept the variable handling rather override |
||
250 | * offsetSet, offsetGet, offsetExists or offsetUnset. |
||
251 | * |
||
252 | * @return mixed |
||
253 | */ |
||
254 | final public function get($name) |
||
255 | { |
||
256 | $parts = explode('.', $name); |
||
257 | |||
258 | $value = $this->findContext($parts); |
||
259 | while ($parts) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
260 | $part = array_shift($parts); |
||
261 | if ($this->arrayKeyExists($value, $part)) { |
||
262 | $value = $this->expand($value[$part]); |
||
263 | } elseif ($this->propertyExists($value, $part)) { |
||
264 | $value = $this->expand($value->$part); |
||
265 | } else { |
||
266 | if (func_num_args() > 1) { |
||
267 | return func_get_arg(1); |
||
268 | } else { |
||
269 | throw new Exception\MissingVariableException('Missing variable ' . $name); |
||
270 | } |
||
271 | } |
||
272 | if ($parts && $value instanceof self && $value !== $this) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
273 | try { |
||
274 | $args = func_get_args(); |
||
275 | $args[0] = implode('.', $parts); |
||
276 | return call_user_func_array(array($value, 'get'), $args); |
||
277 | } catch (Exception\MissingVariableException $e) { |
||
278 | throw new Exception\MissingVariableException('Missing variable ' . $name); |
||
279 | } |
||
280 | } |
||
281 | } |
||
282 | |||
283 | return $value; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Determine if a variable path is available |
||
288 | * |
||
289 | * @param string $name The variable name |
||
290 | * |
||
291 | * @final See {@see Variables::get()} |
||
292 | * |
||
293 | * @return boolean |
||
294 | */ |
||
295 | final public function has($name) |
||
296 | { |
||
297 | $parts = explode('.', $name); |
||
298 | $value = $this->findContext($parts); |
||
299 | while ($parts) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
300 | $part = array_shift($parts); |
||
301 | if ($this->arrayKeyExists($value, $part)) { |
||
302 | $value = $this->expand($value[$part]); |
||
303 | } elseif ($this->propertyExists($value, $part)) { |
||
304 | $value = $this->expand($value->$part); |
||
305 | } else { |
||
306 | return false; |
||
307 | } |
||
308 | if ($parts && $value instanceof self && $value !== $this) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
309 | return $value->has(implode('.', $parts)); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | return true; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * Set a variable by it's path |
||
318 | * |
||
319 | * @param string $name The variable name |
||
320 | * @param mixed $value The value |
||
321 | * |
||
322 | * @final See {@see Variables::get()} |
||
323 | * |
||
324 | * @return $this |
||
325 | */ |
||
326 | final public function set($name, $value) |
||
327 | { |
||
328 | $parts = explode('.', $name); |
||
329 | if (in_array($name, array('this', 'parent'), true)) { |
||
330 | throw new Exception('this and parent are variable names you may not override'); |
||
331 | } |
||
332 | |||
333 | $finalPart = array_pop($parts); |
||
334 | $parent = $this->findContext($parts); |
||
335 | View Code Duplication | while ($parts) { |
|
0 ignored issues
–
show
The expression
$parts 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 ![]() This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
336 | $part = array_shift($parts); |
||
337 | if ($this->arrayKeyExists($parent, $part)) { |
||
338 | $parent = &$parent[$part]; |
||
339 | } elseif ($this->propertyExists($parent, $part)) { |
||
340 | $parent = &$parent->$part; |
||
341 | } else { |
||
342 | $parent = $this->expand($parent); |
||
343 | if (is_object($parent) || is_array($parent)) { |
||
344 | array_unshift($parts, $part); |
||
345 | continue; |
||
346 | } else { |
||
347 | throw new Exception('Can not set values on primitives or null'); |
||
348 | } |
||
349 | } |
||
350 | if ($parts && $parent instanceof self && $parent !== $this) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
351 | $parent->set(implode('.', $parts), $value); |
||
352 | return $this; |
||
353 | } |
||
354 | } |
||
355 | $this->filterSpecialNames($finalPart, $value); |
||
356 | if (is_array($parent) || $parent instanceof \ArrayAccess) { |
||
357 | $parent[$finalPart] = $value; |
||
358 | } elseif (is_object($parent)) { |
||
359 | $parent->$finalPart = $value; |
||
360 | } |
||
361 | return $this; |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * Unset a variable by it's path |
||
366 | * |
||
367 | * @param string $name Variable name |
||
368 | * |
||
369 | * @final See {@see Variables::get()} |
||
370 | * |
||
371 | * @return $this |
||
372 | */ |
||
373 | final public function remove($name) |
||
374 | { |
||
375 | $parts = explode('.', $name); |
||
376 | $finalPart = array_pop($parts); |
||
377 | $parent = $this->findContext($parts); |
||
378 | View Code Duplication | while ($parts) { |
|
0 ignored issues
–
show
The expression
$parts 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 ![]() This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
379 | $part = array_shift($parts); |
||
380 | if ($this->arrayKeyExists($parent, $part)) { |
||
381 | $parent = &$parent[$part]; |
||
382 | } elseif ($this->propertyExists($parent, $part)) { |
||
383 | $parent = &$parent->$part; |
||
384 | } else { |
||
385 | $parent = $this->expand($parent); |
||
386 | if (is_object($parent) || is_array($parent)) { |
||
387 | array_unshift($parts, $part); |
||
388 | continue; |
||
389 | } else { |
||
390 | return $this; |
||
391 | } |
||
392 | } |
||
393 | if ($parts && $parent instanceof self && $parent !== $this) { |
||
0 ignored issues
–
show
The expression
$parts 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 ![]() |
|||
394 | $parent->remove(implode('.', $parts)); |
||
395 | return $this; |
||
396 | } |
||
397 | } |
||
398 | |||
399 | if ($this->arrayKeyExists($parent, $finalPart)) { |
||
400 | unset($parent[$finalPart]); |
||
401 | } elseif ($this->propertyExists($parent, $finalPart)) { |
||
402 | unset($parent->$finalPart); |
||
403 | } |
||
404 | |||
405 | return $this; |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Determine if a variable is an array or accessible as array and has the key |
||
410 | * |
||
411 | * This method is required as isset returns false on existing keys with null |
||
412 | * values and array_key_exists doesn't invoke {@see \ArrayAccess::offsetExists()} |
||
413 | * |
||
414 | * Use this BEFORE {@see Variables::propertyExists()} in according checks, as |
||
415 | * it will match {@see Variables} objects as well. This will likely speed things |
||
416 | * up a little but more importantly it will avoid that private or protected |
||
417 | * properties are detected by {@see Variables::propertyExists()}. |
||
418 | * |
||
419 | * @param array|\ArrayAccess $array Array |
||
420 | * @param string $key The key |
||
421 | * |
||
422 | * @return bool |
||
423 | */ |
||
424 | private function arrayKeyExists($array, $key) |
||
425 | { |
||
426 | if (is_array($array)) { |
||
427 | return array_key_exists($key, $array); |
||
428 | } elseif ($array instanceof \ArrayAccess) { |
||
429 | return $array->offsetExists($key); |
||
430 | } |
||
431 | return false; |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * Determine if a variable is an object and has a property |
||
436 | * |
||
437 | * This method is required as isset returns false on existing properties with |
||
438 | * null values and property_exists doesn't invoke {@see __isset()} |
||
439 | * |
||
440 | * @param object $object The object |
||
441 | * @param string $property The property |
||
442 | * |
||
443 | * @return bool |
||
444 | */ |
||
445 | private function propertyExists($object, $property) |
||
446 | { |
||
447 | if (is_object($object)) { |
||
448 | if (property_exists($object, $property)) { |
||
449 | return true; |
||
450 | } |
||
451 | if (method_exists($object, '__isset') && $object->__isset($property)) { |
||
452 | return true; |
||
453 | } |
||
454 | } |
||
455 | return false; |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Set from array |
||
460 | * |
||
461 | * @param array $values Array with key value pairs |
||
462 | * |
||
463 | * @return $this |
||
464 | */ |
||
465 | public function setFromArray($values) |
||
466 | { |
||
467 | foreach ($values as $key => $value) { |
||
468 | $this->set($key, $value); |
||
469 | } |
||
470 | return $this; |
||
471 | } |
||
472 | |||
473 | /** |
||
474 | * Expand expressions within the $value |
||
475 | * |
||
476 | * @param mixed $value The value |
||
477 | * |
||
478 | * @return string|mixed |
||
479 | */ |
||
480 | public function expand($value) |
||
481 | { |
||
482 | static $expressionEngine; |
||
483 | if (!$expressionEngine) { |
||
484 | $expressionEngine = new ExpressionLanguage(); |
||
485 | } |
||
486 | return $expressionEngine->evaluate($value, [ExpressionLanguage::VARIABLES_KEY => $this]); |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Handles special variable names |
||
491 | * |
||
492 | * @param string $key Current part of the complete variable name |
||
493 | * @param mixed $value The value |
||
494 | * |
||
495 | * @return void |
||
496 | */ |
||
497 | private function filterSpecialNames(&$key, &$value) |
||
498 | { |
||
499 | if ($key === 'node' && is_array($value)) { |
||
500 | $key = 'nodes'; |
||
501 | $value = array($value); |
||
502 | } |
||
503 | if ($key === 'nodes') { |
||
504 | $nodes = array(); |
||
505 | foreach ($value as $id => $options) { |
||
506 | if ($options instanceof Node) { |
||
507 | $node = $options; |
||
508 | } else { |
||
509 | $node = new Node($this); |
||
510 | $node->setFromArray($options); |
||
511 | } |
||
512 | $node->set('id', $id); |
||
513 | $nodes[$id] = $node; |
||
514 | } |
||
515 | $value = $nodes; |
||
516 | } |
||
517 | } |
||
518 | } |
||
519 | ?> |
||
520 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.