PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / utils / src / Utils / Arrays.php

Arrays.php in Packeta trunk, at deps/nette/utils/src/Utils/Arrays.php

371 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace Packetery\Nette\Utils;
9
10 use Packetery\Nette;
11 use function is_array, is_int, is_object, count;
12 /**
13 * Array tools library.
14 */
15 class Arrays
16 {
17 use \Packetery\Nette\StaticClass;
18 /**
19 * Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
20 * @template T
21 * @param array<T> $array
22 * @param array-key|array-key[] $key
23 * @param ?T $default
24 * @return ?T
25 * @throws \Packetery\Nette\InvalidArgumentException if item does not exist and default value is not provided
26 */
27 public static function get(array $array, $key, $default = null)
28 {
29 foreach (is_array($key) ? $key : [$key] as $k) {
30 if (is_array($array) && \array_key_exists($k, $array)) {
31 $array = $array[$k];
32 } else {
33 if (\func_num_args() < 3) {
34 throw new \Packetery\Nette\InvalidArgumentException("Missing item '{$k}'.");
35 }
36 return $default;
37 }
38 }
39 return $array;
40 }
41 /**
42 * Returns reference to array item. If the index does not exist, new one is created with value null.
43 * @template T
44 * @param array<T> $array
45 * @param array-key|array-key[] $key
46 * @return ?T
47 * @throws \Packetery\Nette\InvalidArgumentException if traversed item is not an array
48 */
49 public static function &getRef(array &$array, $key)
50 {
51 foreach (is_array($key) ? $key : [$key] as $k) {
52 if (is_array($array) || $array === null) {
53 $array =& $array[$k];
54 } else {
55 throw new \Packetery\Nette\InvalidArgumentException('Traversed item is not an array.');
56 }
57 }
58 return $array;
59 }
60 /**
61 * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
62 * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
63 * the value from the first array in the case of a key collision.
64 * @template T1
65 * @template T2
66 * @param array<T1> $array1
67 * @param array<T2> $array2
68 * @return array<T1|T2>
69 */
70 public static function mergeTree(array $array1, array $array2) : array
71 {
72 $res = $array1 + $array2;
73 foreach (\array_intersect_key($array1, $array2) as $k => $v) {
74 if (is_array($v) && is_array($array2[$k])) {
75 $res[$k] = self::mergeTree($v, $array2[$k]);
76 }
77 }
78 return $res;
79 }
80 /**
81 * Returns zero-indexed position of given array key. Returns null if key is not found.
82 * @param array-key $key
83 * @return int|null offset if it is found, null otherwise
84 */
85 public static function getKeyOffset(array $array, $key) : ?int
86 {
87 return Helpers::falseToNull(\array_search(self::toKey($key), \array_keys($array), \true));
88 }
89 /**
90 * @deprecated use getKeyOffset()
91 */
92 public static function searchKey(array $array, $key) : ?int
93 {
94 return self::getKeyOffset($array, $key);
95 }
96 /**
97 * Tests an array for the presence of value.
98 * @param mixed $value
99 */
100 public static function contains(array $array, $value) : bool
101 {
102 return \in_array($value, $array, \true);
103 }
104 /**
105 * Returns the first item from the array or null if array is empty.
106 * @template T
107 * @param array<T> $array
108 * @return ?T
109 */
110 public static function first(array $array)
111 {
112 return count($array) ? \reset($array) : null;
113 }
114 /**
115 * Returns the last item from the array or null if array is empty.
116 * @template T
117 * @param array<T> $array
118 * @return ?T
119 */
120 public static function last(array $array)
121 {
122 return count($array) ? \end($array) : null;
123 }
124 /**
125 * Inserts the contents of the $inserted array into the $array immediately after the $key.
126 * If $key is null (or does not exist), it is inserted at the beginning.
127 * @param array-key|null $key
128 */
129 public static function insertBefore(array &$array, $key, array $inserted) : void
130 {
131 $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
132 $array = \array_slice($array, 0, $offset, \true) + $inserted + \array_slice($array, $offset, count($array), \true);
133 }
134 /**
135 * Inserts the contents of the $inserted array into the $array before the $key.
136 * If $key is null (or does not exist), it is inserted at the end.
137 * @param array-key|null $key
138 */
139 public static function insertAfter(array &$array, $key, array $inserted) : void
140 {
141 if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
142 $offset = count($array) - 1;
143 }
144 $array = \array_slice($array, 0, $offset + 1, \true) + $inserted + \array_slice($array, $offset + 1, count($array), \true);
145 }
146 /**
147 * Renames key in array.
148 * @param array-key $oldKey
149 * @param array-key $newKey
150 */
151 public static function renameKey(array &$array, $oldKey, $newKey) : bool
152 {
153 $offset = self::getKeyOffset($array, $oldKey);
154 if ($offset === null) {
155 return \false;
156 }
157 $val =& $array[$oldKey];
158 $keys = \array_keys($array);
159 $keys[$offset] = $newKey;
160 $array = \array_combine($keys, $array);
161 $array[$newKey] =& $val;
162 return \true;
163 }
164 /**
165 * Returns only those array items, which matches a regular expression $pattern.
166 * @param string[] $array
167 * @return string[]
168 */
169 public static function grep(array $array, string $pattern, int $flags = 0) : array
170 {
171 return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
172 }
173 /**
174 * Transforms multidimensional array to flat array.
175 */
176 public static function flatten(array $array, bool $preserveKeys = \false) : array
177 {
178 $res = [];
179 $cb = $preserveKeys ? function ($v, $k) use(&$res) : void {
180 $res[$k] = $v;
181 } : function ($v) use(&$res) : void {
182 $res[] = $v;
183 };
184 \array_walk_recursive($array, $cb);
185 return $res;
186 }
187 /**
188 * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
189 * @param mixed $value
190 */
191 public static function isList($value) : bool
192 {
193 return is_array($value) && (\PHP_VERSION_ID < 80100 ? !$value || \array_keys($value) === \range(0, count($value) - 1) : \array_is_list($value));
194 }
195 /**
196 * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
197 * @param string|string[] $path
198 * @return array|\stdClass
199 */
200 public static function associate(array $array, $path)
201 {
202 $parts = is_array($path) ? $path : \preg_split('#(\\[\\]|->|=|\\|)#', $path, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
203 if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
204 throw new \Packetery\Nette\InvalidArgumentException("Invalid path '{$path}'.");
205 }
206 $res = $parts[0] === '->' ? new \stdClass() : [];
207 foreach ($array as $rowOrig) {
208 $row = (array) $rowOrig;
209 $x =& $res;
210 for ($i = 0; $i < count($parts); $i++) {
211 $part = $parts[$i];
212 if ($part === '[]') {
213 $x =& $x[];
214 } elseif ($part === '=') {
215 if (isset($parts[++$i])) {
216 $x = $row[$parts[$i]];
217 $row = null;
218 }
219 } elseif ($part === '->') {
220 if (isset($parts[++$i])) {
221 if ($x === null) {
222 $x = new \stdClass();
223 }
224 $x =& $x->{$row[$parts[$i]]};
225 } else {
226 $row = is_object($rowOrig) ? $rowOrig : (object) $row;
227 }
228 } elseif ($part !== '|') {
229 $x =& $x[(string) $row[$part]];
230 }
231 }
232 if ($x === null) {
233 $x = $row;
234 }
235 }
236 return $res;
237 }
238 /**
239 * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
240 * @param mixed $filling
241 */
242 public static function normalize(array $array, $filling = null) : array
243 {
244 $res = [];
245 foreach ($array as $k => $v) {
246 $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
247 }
248 return $res;
249 }
250 /**
251 * Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
252 * or returns $default, if provided.
253 * @template T
254 * @param array<T> $array
255 * @param array-key $key
256 * @param ?T $default
257 * @return ?T
258 * @throws \Packetery\Nette\InvalidArgumentException if item does not exist and default value is not provided
259 */
260 public static function pick(array &$array, $key, $default = null)
261 {
262 if (\array_key_exists($key, $array)) {
263 $value = $array[$key];
264 unset($array[$key]);
265 return $value;
266 } elseif (\func_num_args() < 3) {
267 throw new \Packetery\Nette\InvalidArgumentException("Missing item '{$key}'.");
268 } else {
269 return $default;
270 }
271 }
272 /**
273 * Tests whether at least one element in the array passes the test implemented by the
274 * provided callback with signature `function ($value, $key, array $array): bool`.
275 */
276 public static function some(iterable $array, callable $callback) : bool
277 {
278 foreach ($array as $k => $v) {
279 if ($callback($v, $k, $array)) {
280 return \true;
281 }
282 }
283 return \false;
284 }
285 /**
286 * Tests whether all elements in the array pass the test implemented by the provided function,
287 * which has the signature `function ($value, $key, array $array): bool`.
288 */
289 public static function every(iterable $array, callable $callback) : bool
290 {
291 foreach ($array as $k => $v) {
292 if (!$callback($v, $k, $array)) {
293 return \false;
294 }
295 }
296 return \true;
297 }
298 /**
299 * Calls $callback on all elements in the array and returns the array of return values.
300 * The callback has the signature `function ($value, $key, array $array): bool`.
301 */
302 public static function map(iterable $array, callable $callback) : array
303 {
304 $res = [];
305 foreach ($array as $k => $v) {
306 $res[$k] = $callback($v, $k, $array);
307 }
308 return $res;
309 }
310 /**
311 * Invokes all callbacks and returns array of results.
312 * @param callable[] $callbacks
313 */
314 public static function invoke(iterable $callbacks, ...$args) : array
315 {
316 $res = [];
317 foreach ($callbacks as $k => $cb) {
318 $res[$k] = $cb(...$args);
319 }
320 return $res;
321 }
322 /**
323 * Invokes method on every object in an array and returns array of results.
324 * @param object[] $objects
325 */
326 public static function invokeMethod(iterable $objects, string $method, ...$args) : array
327 {
328 $res = [];
329 foreach ($objects as $k => $obj) {
330 $res[$k] = $obj->{$method}(...$args);
331 }
332 return $res;
333 }
334 /**
335 * Copies the elements of the $array array to the $object object and then returns it.
336 * @template T of object
337 * @param T $object
338 * @return T
339 */
340 public static function toObject(iterable $array, $object)
341 {
342 foreach ($array as $k => $v) {
343 $object->{$k} = $v;
344 }
345 return $object;
346 }
347 /**
348 * Converts value to array key.
349 * @param mixed $value
350 * @return array-key
351 */
352 public static function toKey($value)
353 {
354 return \key([$value => null]);
355 }
356 /**
357 * Returns copy of the $array where every item is converted to string
358 * and prefixed by $prefix and suffixed by $suffix.
359 * @param string[] $array
360 * @return string[]
361 */
362 public static function wrap(array $array, string $prefix = '', string $suffix = '') : array
363 {
364 $res = [];
365 foreach ($array as $k => $v) {
366 $res[$k] = $prefix . $v . $suffix;
367 }
368 return $res;
369 }
370 }
371