Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 150 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

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