PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / s3 / JmesPath / TreeInterpreter.php

TreeInterpreter.php in Media Cloud Sync 1.3.11, at includes/sdk/s3/JmesPath/TreeInterpreter.php

183 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\JmesPath;
4
5 /**
6 * Tree visitor used to evaluates JMESPath AST expressions.
7 */
8 class TreeInterpreter
9 {
10 /** @var callable */
11 private $fnDispatcher;
12 /**
13 * @param callable|null $fnDispatcher Function dispatching function that accepts
14 * a function name argument and an array of
15 * function arguments and returns the result.
16 */
17 public function __construct(?callable $fnDispatcher = null)
18 {
19 $this->fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
20 }
21 /**
22 * Visits each node in a JMESPath AST and returns the evaluated result.
23 *
24 * @param array $node JMESPath AST node
25 * @param mixed $data Data to evaluate
26 *
27 * @return mixed
28 */
29 public function visit(array $node, $data)
30 {
31 return $this->dispatch($node, $data);
32 }
33 /**
34 * Recursively traverses an AST using depth-first, pre-order traversal.
35 * The evaluation logic for each node type is embedded into a large switch
36 * statement to avoid the cost of "double dispatch".
37 * @return mixed
38 */
39 private function dispatch(array $node, $value)
40 {
41 $dispatcher = $this->fnDispatcher;
42 switch ($node['type']) {
43 case 'field':
44 if (\is_array($value) || $value instanceof \ArrayAccess) {
45 return isset($value[$node['value']]) ? $value[$node['value']] : null;
46 } elseif ($value instanceof \stdClass) {
47 return isset($value->{$node['value']}) ? $value->{$node['value']} : null;
48 }
49 return null;
50 case 'subexpression':
51 return $this->dispatch($node['children'][1], $this->dispatch($node['children'][0], $value));
52 case 'index':
53 if (!Utils::isArray($value)) {
54 return null;
55 }
56 $idx = $node['value'] >= 0 ? $node['value'] : $node['value'] + \count($value);
57 return isset($value[$idx]) ? $value[$idx] : null;
58 case 'projection':
59 $left = $this->dispatch($node['children'][0], $value);
60 switch ($node['from']) {
61 case 'object':
62 if (!Utils::isObject($left)) {
63 return null;
64 }
65 break;
66 case 'array':
67 if (!Utils::isArray($left)) {
68 return null;
69 }
70 break;
71 default:
72 if (!\is_array($left) || !$left instanceof \stdClass) {
73 return null;
74 }
75 }
76 $collected = [];
77 foreach ((array) $left as $val) {
78 $result = $this->dispatch($node['children'][1], $val);
79 if ($result !== null) {
80 $collected[] = $result;
81 }
82 }
83 return $collected;
84 case 'flatten':
85 static $skipElement = [];
86 $value = $this->dispatch($node['children'][0], $value);
87 if (!Utils::isArray($value)) {
88 return null;
89 }
90 $merged = [];
91 foreach ($value as $values) {
92 // Only merge up arrays lists and not hashes
93 if (\is_array($values) && \array_key_exists(0, $values)) {
94 $merged = \array_merge($merged, $values);
95 } elseif ($values !== $skipElement) {
96 $merged[] = $values;
97 }
98 }
99 return $merged;
100 case 'literal':
101 return $node['value'];
102 case 'current':
103 return $value;
104 case 'or':
105 $result = $this->dispatch($node['children'][0], $value);
106 return Utils::isTruthy($result) ? $result : $this->dispatch($node['children'][1], $value);
107 case 'and':
108 $result = $this->dispatch($node['children'][0], $value);
109 return Utils::isTruthy($result) ? $this->dispatch($node['children'][1], $value) : $result;
110 case 'not':
111 return !Utils::isTruthy($this->dispatch($node['children'][0], $value));
112 case 'pipe':
113 return $this->dispatch($node['children'][1], $this->dispatch($node['children'][0], $value));
114 case 'multi_select_list':
115 if ($value === null) {
116 return null;
117 }
118 $collected = [];
119 foreach ($node['children'] as $node) {
120 $collected[] = $this->dispatch($node, $value);
121 }
122 return $collected;
123 case 'multi_select_hash':
124 if ($value === null) {
125 return null;
126 }
127 $collected = [];
128 foreach ($node['children'] as $node) {
129 $collected[$node['value']] = $this->dispatch($node['children'][0], $value);
130 }
131 return $collected;
132 case 'comparator':
133 $left = $this->dispatch($node['children'][0], $value);
134 $right = $this->dispatch($node['children'][1], $value);
135 if ($node['value'] == '==') {
136 return Utils::isEqual($left, $right);
137 } elseif ($node['value'] == '!=') {
138 return !Utils::isEqual($left, $right);
139 } else {
140 return self::relativeCmp($left, $right, $node['value']);
141 }
142 case 'condition':
143 return Utils::isTruthy($this->dispatch($node['children'][0], $value)) ? $this->dispatch($node['children'][1], $value) : null;
144 case 'function':
145 $args = [];
146 foreach ($node['children'] as $arg) {
147 $args[] = $this->dispatch($arg, $value);
148 }
149 return $dispatcher($node['value'], $args);
150 case 'slice':
151 return \is_string($value) || Utils::isArray($value) ? Utils::slice($value, $node['value'][0], $node['value'][1], $node['value'][2]) : null;
152 case 'expref':
153 $apply = $node['children'][0];
154 return function ($value) use($apply) {
155 return $this->visit($apply, $value);
156 };
157 default:
158 throw new \RuntimeException("Unknown node type: {$node['type']}");
159 }
160 }
161 /**
162 * @return bool
163 */
164 private static function relativeCmp($left, $right, $cmp)
165 {
166 if (!(\is_int($left) || \is_float($left)) || !(\is_int($right) || \is_float($right))) {
167 return \false;
168 }
169 switch ($cmp) {
170 case '>':
171 return $left > $right;
172 case '>=':
173 return $left >= $right;
174 case '<':
175 return $left < $right;
176 case '<=':
177 return $left <= $right;
178 default:
179 throw new \RuntimeException("Invalid comparison: {$cmp}");
180 }
181 }
182 }
183