PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
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 / FnDispatcher.php

FnDispatcher.php in Media Cloud Sync trunk, at includes/sdk/s3/JmesPath/FnDispatcher.php

342 lines 11.8 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 * Dispatches to named JMESPath functions using a single function that has the
7 * following signature:
8 *
9 * mixed $result = fn(string $function_name, array $args)
10 */
11 class FnDispatcher
12 {
13 /**
14 * Gets a cached instance of the default function implementations.
15 *
16 * @return FnDispatcher
17 */
18 public static function getInstance()
19 {
20 static $instance = null;
21 if (!$instance) {
22 $instance = new self();
23 }
24 return $instance;
25 }
26 /**
27 * @param string $fn Function name.
28 * @param array $args Function arguments.
29 *
30 * @return mixed
31 */
32 public function __invoke($fn, array $args)
33 {
34 return $this->{'fn_' . $fn}($args);
35 }
36 private function fn_abs(array $args)
37 {
38 $this->validate('abs', $args, [['number']]);
39 return \abs($args[0]);
40 }
41 private function fn_avg(array $args)
42 {
43 $this->validate('avg', $args, [['array']]);
44 $sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) {
45 return Utils::add($a, $b);
46 });
47 return $args[0] ? $sum / \count($args[0]) : null;
48 }
49 private function fn_ceil(array $args)
50 {
51 $this->validate('ceil', $args, [['number']]);
52 return \ceil($args[0]);
53 }
54 private function fn_contains(array $args)
55 {
56 $this->validate('contains', $args, [['string', 'array'], ['any']]);
57 if (\is_array($args[0])) {
58 return \in_array($args[1], $args[0]);
59 } elseif (\is_string($args[1])) {
60 return \mb_strpos($args[0], $args[1], 0, 'UTF-8') !== \false;
61 } else {
62 return null;
63 }
64 }
65 private function fn_ends_with(array $args)
66 {
67 $this->validate('ends_with', $args, [['string'], ['string']]);
68 list($search, $suffix) = $args;
69 return $suffix === '' || \mb_substr($search, -\mb_strlen($suffix, 'UTF-8'), null, 'UTF-8') === $suffix;
70 }
71 private function fn_floor(array $args)
72 {
73 $this->validate('floor', $args, [['number']]);
74 return \floor($args[0]);
75 }
76 private function fn_not_null(array $args)
77 {
78 if (!$args) {
79 throw new \RuntimeException("not_null() expects 1 or more arguments, 0 were provided");
80 }
81 return \array_reduce($args, function ($carry, $item) {
82 return $carry !== null ? $carry : $item;
83 });
84 }
85 private function fn_join(array $args)
86 {
87 $this->validate('join', $args, [['string'], ['array']]);
88 $fn = function ($a, $b, $i) use($args) {
89 return $i ? $a . $args[0] . $b : $b;
90 };
91 return $this->reduce('join:0', $args[1], ['string'], $fn);
92 }
93 private function fn_keys(array $args)
94 {
95 $this->validate('keys', $args, [['object']]);
96 return \array_keys((array) $args[0]);
97 }
98 private function fn_length(array $args)
99 {
100 $this->validate('length', $args, [['string', 'array', 'object']]);
101 return \is_string($args[0]) ? \mb_strlen($args[0], 'UTF-8') : \count((array) $args[0]);
102 }
103 private function fn_max(array $args)
104 {
105 $this->validate('max', $args, [['array']]);
106 $fn = function ($a, $b) {
107 return $a >= $b ? $a : $b;
108 };
109 return $this->reduce('max:0', $args[0], ['number', 'string'], $fn);
110 }
111 private function fn_max_by(array $args)
112 {
113 $this->validate('max_by', $args, [['array'], ['expression']]);
114 $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']);
115 $fn = function ($carry, $item, $index) use($expr) {
116 return $index ? $expr($carry) >= $expr($item) ? $carry : $item : $item;
117 };
118 return $this->reduce('max_by:1', $args[0], ['any'], $fn);
119 }
120 private function fn_min(array $args)
121 {
122 $this->validate('min', $args, [['array']]);
123 $fn = function ($a, $b, $i) {
124 return $i && $a <= $b ? $a : $b;
125 };
126 return $this->reduce('min:0', $args[0], ['number', 'string'], $fn);
127 }
128 private function fn_min_by(array $args)
129 {
130 $this->validate('min_by', $args, [['array'], ['expression']]);
131 $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']);
132 $i = -1;
133 $fn = function ($a, $b) use($expr, &$i) {
134 return ++$i ? $expr($a) <= $expr($b) ? $a : $b : $b;
135 };
136 return $this->reduce('min_by:1', $args[0], ['any'], $fn);
137 }
138 private function fn_reverse(array $args)
139 {
140 $this->validate('reverse', $args, [['array', 'string']]);
141 if (\is_array($args[0])) {
142 return \array_reverse($args[0]);
143 } elseif (\is_string($args[0])) {
144 return \strrev($args[0]);
145 } else {
146 throw new \RuntimeException('Cannot reverse provided argument');
147 }
148 }
149 private function fn_sum(array $args)
150 {
151 $this->validate('sum', $args, [['array']]);
152 $fn = function ($a, $b) {
153 return Utils::add($a, $b);
154 };
155 return $this->reduce('sum:0', $args[0], ['number'], $fn);
156 }
157 private function fn_sort(array $args)
158 {
159 $this->validate('sort', $args, [['array']]);
160 $valid = ['string', 'number'];
161 return Utils::stableSort($args[0], function ($a, $b) use($valid) {
162 $this->validateSeq('sort:0', $valid, $a, $b);
163 return \strnatcmp($a, $b);
164 });
165 }
166 private function fn_sort_by(array $args)
167 {
168 $this->validate('sort_by', $args, [['array'], ['expression']]);
169 $expr = $args[1];
170 $valid = ['string', 'number'];
171 return Utils::stableSort($args[0], function ($a, $b) use($expr, $valid) {
172 $va = $expr($a);
173 $vb = $expr($b);
174 $this->validateSeq('sort_by:0', $valid, $va, $vb);
175 return \strnatcmp($va, $vb);
176 });
177 }
178 private function fn_starts_with(array $args)
179 {
180 $this->validate('starts_with', $args, [['string'], ['string']]);
181 list($search, $prefix) = $args;
182 return $prefix === '' || \mb_strpos($search, $prefix, 0, 'UTF-8') === 0;
183 }
184 private function fn_type(array $args)
185 {
186 $this->validateArity('type', \count($args), 1);
187 return Utils::type($args[0]);
188 }
189 private function fn_to_string(array $args)
190 {
191 $this->validateArity('to_string', \count($args), 1);
192 $v = $args[0];
193 if (\is_string($v)) {
194 return $v;
195 } elseif (\is_object($v) && !$v instanceof \JsonSerializable && \method_exists($v, '__toString')) {
196 return (string) $v;
197 }
198 return \json_encode($v);
199 }
200 private function fn_to_number(array $args)
201 {
202 $this->validateArity('to_number', \count($args), 1);
203 $value = $args[0];
204 $type = Utils::type($value);
205 if ($type == 'number') {
206 return $value;
207 } elseif ($type == 'string' && \is_numeric($value)) {
208 return \mb_strpos($value, '.', 0, 'UTF-8') ? (float) $value : (int) $value;
209 } else {
210 return null;
211 }
212 }
213 private function fn_values(array $args)
214 {
215 $this->validate('values', $args, [['array', 'object']]);
216 return \array_values((array) $args[0]);
217 }
218 private function fn_merge(array $args)
219 {
220 if (!$args) {
221 throw new \RuntimeException("merge() expects 1 or more arguments, 0 were provided");
222 }
223 return \call_user_func_array('array_replace', $args);
224 }
225 private function fn_to_array(array $args)
226 {
227 $this->validate('to_array', $args, [['any']]);
228 return Utils::isArray($args[0]) ? $args[0] : [$args[0]];
229 }
230 private function fn_map(array $args)
231 {
232 $this->validate('map', $args, [['expression'], ['any']]);
233 $result = [];
234 foreach ($args[1] as $a) {
235 $result[] = $args[0]($a);
236 }
237 return $result;
238 }
239 private function typeError($from, $msg)
240 {
241 if (\mb_strpos($from, ':', 0, 'UTF-8')) {
242 list($fn, $pos) = \explode(':', $from);
243 throw new \RuntimeException(\sprintf('Argument %d of %s %s', $pos, $fn, $msg));
244 } else {
245 throw new \RuntimeException(\sprintf('Type error: %s %s', $from, $msg));
246 }
247 }
248 private function validateArity($from, $given, $expected)
249 {
250 if ($given != $expected) {
251 $err = "%s() expects {$expected} arguments, {$given} were provided";
252 throw new \RuntimeException(\sprintf($err, $from));
253 }
254 }
255 private function validate($from, $args, $types = [])
256 {
257 $this->validateArity($from, \count($args), \count($types));
258 foreach ($args as $index => $value) {
259 if (!isset($types[$index]) || !$types[$index]) {
260 continue;
261 }
262 $this->validateType("{$from}:{$index}", $value, $types[$index]);
263 }
264 }
265 private function validateType($from, $value, array $types)
266 {
267 if ($types[0] == 'any' || \in_array(Utils::type($value), $types) || $value === [] && \in_array('object', $types)) {
268 return;
269 }
270 $msg = 'must be one of the following types: ' . \implode(', ', $types) . '. ' . Utils::type($value) . ' found';
271 $this->typeError($from, $msg);
272 }
273 /**
274 * Validates value A and B, ensures they both are correctly typed, and of
275 * the same type.
276 *
277 * @param string $from String of function:argument_position
278 * @param array $types Array of valid value types.
279 * @param mixed $a Value A
280 * @param mixed $b Value B
281 */
282 private function validateSeq($from, array $types, $a, $b)
283 {
284 $ta = Utils::type($a);
285 $tb = Utils::type($b);
286 if ($ta !== $tb) {
287 $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";
288 $this->typeError($from, $msg);
289 }
290 $typeMatch = $types && $types[0] == 'any' || \in_array($ta, $types);
291 if (!$typeMatch) {
292 $msg = 'encountered a type error in sequence. The argument must be ' . 'an array of ' . \implode('|', $types) . ' types. ' . "Found {$ta}, {$tb}.";
293 $this->typeError($from, $msg);
294 }
295 }
296 /**
297 * Reduces and validates an array of values to a single value using a fn.
298 *
299 * @param string $from String of function:argument_position
300 * @param array $values Values to reduce.
301 * @param array $types Array of valid value types.
302 * @param callable $reduce Reduce function that accepts ($carry, $item).
303 *
304 * @return mixed
305 */
306 private function reduce($from, array $values, array $types, callable $reduce)
307 {
308 $i = -1;
309 return \array_reduce($values, function ($carry, $item) use($from, $types, $reduce, &$i) {
310 if (++$i > 0) {
311 $this->validateSeq($from, $types, $carry, $item);
312 }
313 return $reduce($carry, $item, $i);
314 });
315 }
316 /**
317 * Validates the return values of expressions as they are applied.
318 *
319 * @param string $from Function name : position
320 * @param callable $expr Expression function to validate.
321 * @param array $types Array of acceptable return type values.
322 *
323 * @return callable Returns a wrapped function
324 */
325 private function wrapExpression($from, callable $expr, array $types)
326 {
327 list($fn, $pos) = \explode(':', $from);
328 $from = "The expression return value of argument {$pos} of {$fn}";
329 return function ($value) use($from, $expr, $types) {
330 $value = $expr($value);
331 $this->validateType($from, $value, $types);
332 return $value;
333 };
334 }
335 /** @internal Pass function name validation off to runtime */
336 public function __call($name, $args)
337 {
338 $name = \str_replace('fn_', '', $name);
339 throw new \RuntimeException("Call to undefined function {$name}");
340 }
341 }
342