unyx /
utils
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 namespace nyx\utils; |
||
| 2 | |||
| 3 | // External dependencies |
||
| 4 | use nyx\diagnostics; |
||
| 5 | |||
| 6 | /** |
||
| 7 | * Arr |
||
| 8 | * |
||
| 9 | * The Arr class provides a few helper methods to make dealing with arrays easier. |
||
| 10 | * |
||
| 11 | * All methods which work with string delimited keys accept a string delimiter. If none is given (ie. null is passed), |
||
| 12 | * the default delimiter (dot) set statically in this class will be used. |
||
| 13 | * |
||
| 14 | * Some code in this class can be simplified and some duplication could be avoided but it is laid out so that the |
||
| 15 | * most common use cases are checked for first with performance being the priority. |
||
| 16 | * |
||
| 17 | * Some methods have aliases. To avoid the overhead please use the base methods, not the aliases. The methods which |
||
| 18 | * have aliases are documented as such and each alias directly points to the base method. |
||
| 19 | * |
||
| 20 | * Note: This class is based on Laravel, FuelPHP, Lo-dash and a few others, but certain methods which appear in |
||
| 21 | * those are not included since they would add overhead we consider 'not worth it' and don't want to encourage |
||
| 22 | * the use thereof: |
||
| 23 | * |
||
| 24 | * - Arr::each() -> use array_map() instead. |
||
| 25 | * - Arr::filter(), Arr::reject() -> use array_filter() instead. |
||
| 26 | * - Arr::range() -> use range() instead. |
||
| 27 | * - Arr::repeat() -> use array_fill() instead. |
||
| 28 | * - Arr::search() -> use array_search() instead. |
||
| 29 | * - Arr::shuffle() -> use shuffle() instead. |
||
| 30 | * - Arr::size() -> use count() instead. |
||
| 31 | * |
||
| 32 | * @package Nyx\Utils |
||
| 33 | * @version 0.1.0 |
||
| 34 | * @author Michal Chojnacki <[email protected]> |
||
| 35 | * @copyright 2012-2016 Nyx Dev Team |
||
| 36 | * @link http://docs.muyo.io/nyx/utils/index.html |
||
| 37 | * @todo Arr::sort() and Arr:sortBy() (add sortBy() to core\traits\Collection as well). |
||
| 38 | * @todo Add ArrayObject support? How? Just strip the array type hints so as to not add overhead with checks? |
||
| 39 | */ |
||
| 40 | class Arr |
||
| 41 | { |
||
| 42 | /** |
||
| 43 | * The traits of the Arr class. |
||
| 44 | */ |
||
| 45 | use traits\StaticallyExtendable; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @var string The default delimiter to use to separate array dimensions. |
||
| 49 | */ |
||
| 50 | public static $delimiter = '.'; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Adds an element to the given array but only if it does not yet exist. |
||
| 54 | * |
||
| 55 | * Note: Null as value of an item is considered a non-existing item for the purposes |
||
| 56 | * of this method. |
||
| 57 | * |
||
| 58 | * @param array $array The array to which the element should be added. |
||
| 59 | * @param string $key The key at which the value should be added. |
||
| 60 | * @param mixed $value The value of the element. |
||
| 61 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 62 | */ |
||
| 63 | public static function add(array& $array, string $key, $value, string $delimiter = null) |
||
| 64 | { |
||
| 65 | if (null === static::get($array, $key, null, $delimiter)) { |
||
| 66 | static::set($array, $key, $value, $delimiter); |
||
| 67 | } |
||
| 68 | } |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Checks whether all elements in the given array pass the given truth test. |
||
| 72 | * |
||
| 73 | * Aliases: |
||
| 74 | * - @see Arr::every() |
||
| 75 | * |
||
| 76 | * @param array $array The array to traverse. |
||
| 77 | * @param callable $callback The truth test the elements should pass. |
||
| 78 | * @return bool True when the elements passed the truth test, false otherwise. |
||
| 79 | */ |
||
| 80 | public static function all(array $array, callable $callback) : bool |
||
| 81 | { |
||
| 82 | // Map the array and then search for a 'false' boolean. If none is found, |
||
| 83 | // we assume all elements passed the test. |
||
| 84 | return !in_array(false, array_map($callback, $array), true); |
||
| 85 | } |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Checks whether any of the elements in the given array passes the given truth test. |
||
| 89 | * |
||
| 90 | * Aliases: |
||
| 91 | * - @see Arr::some() |
||
| 92 | * |
||
| 93 | * @param array $array The array to traverse. |
||
| 94 | * @param callable $callback The truth test the elements should pass. |
||
| 95 | * @return bool True when at least on the the elements passed the truth test, false |
||
| 96 | * otherwise. |
||
| 97 | */ |
||
| 98 | public static function any(array $array, callable $callback) : bool |
||
| 99 | { |
||
| 100 | // Map the array and then search for a 'true' boolean. If at least one is found, |
||
| 101 | // we assume at least one element passed the test. |
||
| 102 | return in_array(true, array_map($callback, $array), true); |
||
| 103 | } |
||
| 104 | |||
| 105 | /** |
||
| 106 | * Returns the average value of the given array. |
||
| 107 | * |
||
| 108 | * @param array $array The array to traverse. |
||
| 109 | * @param int $decimals The number of decimals to return. |
||
| 110 | * @return float The average value. |
||
| 111 | */ |
||
| 112 | public static function average(array $array, int $decimals = 0) : float |
||
| 113 | { |
||
| 114 | return round((array_sum($array) / count($array)), $decimals); |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Removes all elements containing falsy values from the given array. The keys are preserved. |
||
| 119 | * |
||
| 120 | * See {@link http://php.net/manual/en/language.types.boolean.php} for information on which values evaluate |
||
| 121 | * to false. |
||
| 122 | * |
||
| 123 | * @param array $array The array to traverse. |
||
| 124 | * @return array The resulting array. |
||
| 125 | */ |
||
| 126 | public static function clean(array $array) : array |
||
| 127 | { |
||
| 128 | return array_filter($array, function($value) { |
||
| 129 | return (bool) $value; |
||
| 130 | }); |
||
| 131 | } |
||
| 132 | |||
| 133 | /** |
||
| 134 | * Collapses an array of arrays into a single array. Not recursive, ie. only the first dimension of arrays |
||
| 135 | * will be collapsed down. |
||
| 136 | * |
||
| 137 | * Standard array_merge() rules apply - @link http://php.net/manual/en/function.array-merge.php - |
||
| 138 | * non-array values with numeric keys will be appended to the resulting array in the order they are given, while |
||
| 139 | * non-array values with non-numeric keys will have their keys preserved but the values may be overwritten by |
||
| 140 | * the nested arrays being collapsed down if those contain values with the same non-numeric keys. Latter arrays |
||
| 141 | * overwrite previous arrays' keys on collisions. |
||
| 142 | * |
||
| 143 | * @param array $array The array to collapse. |
||
| 144 | * @return array The resulting array. |
||
| 145 | */ |
||
| 146 | public static function collapse(array $array) : array |
||
| 147 | { |
||
| 148 | $results = []; |
||
| 149 | |||
| 150 | foreach ($array as $key => $item) { |
||
| 151 | |||
| 152 | // Nested arrays will be merged in (non-recursively). |
||
| 153 | if (is_array($item)) { |
||
| 154 | $results = array_merge($results, $item); continue; |
||
| 155 | } |
||
| 156 | |||
| 157 | // Values with numeric keys will be appended in any case. |
||
| 158 | if (is_int($key)) { |
||
| 159 | $results[] = $item; continue; |
||
| 160 | } |
||
| 161 | |||
| 162 | // Non-numeric keys. If we've got the given key in $results already, it means it was merged |
||
| 163 | // in from one of the nested arrays and those are meant to overwrite the initial values on collisions - |
||
| 164 | // thus we're not going to do anything in that case. |
||
| 165 | if (!array_key_exists($key, $results)) { |
||
| 166 | $results[$key] = $item; |
||
| 167 | } |
||
| 168 | } |
||
| 169 | |||
| 170 | return $results; |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Checks whether the given value is contained within the given array. Equivalent of a recursive in_array. |
||
| 175 | * When you are sure you are dealing with a 1-dimensional array, use in_array instead to avoid the overhead. |
||
| 176 | * |
||
| 177 | * @param array $haystack The array to search in. |
||
| 178 | * @param mixed $needle The value to search for. |
||
| 179 | * @param bool $strict Whether strict equality matches should be performed on the values. |
||
| 180 | * @return bool True when the value was found in the array, false otherwise. |
||
| 181 | */ |
||
| 182 | public static function contains(array $haystack, $needle, bool $strict = true) |
||
| 183 | { |
||
| 184 | foreach ($haystack as $value) { |
||
| 185 | if ((!$strict && $needle == $value) || $needle === $value) { |
||
| 186 | return true; |
||
| 187 | } |
||
| 188 | |||
| 189 | if (is_array($value) && static::contains($needle, $value, $strict)) { |
||
| 190 | return true; |
||
| 191 | } |
||
| 192 | } |
||
| 193 | |||
| 194 | return false; |
||
| 195 | } |
||
| 196 | |||
| 197 | /** |
||
| 198 | * Flattens a multi-dimensional array using the given delimiter. |
||
| 199 | * |
||
| 200 | * @param array $array The initial array. |
||
| 201 | * @param string $prepend A string that should be prepended to the keys. |
||
| 202 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 203 | * @return array The resulting array. |
||
| 204 | */ |
||
| 205 | public static function delimit(array $array, string $prepend = '', string $delimiter = null) |
||
| 206 | { |
||
| 207 | // Results holder. |
||
| 208 | $results = []; |
||
| 209 | |||
| 210 | // Which string delimiter should we use? |
||
| 211 | if (null === $delimiter) { |
||
| 212 | $delimiter = static::$delimiter; |
||
| 213 | } |
||
| 214 | |||
| 215 | foreach ($array as $key => $value) { |
||
| 216 | if (is_array($value) && !empty($value)) { |
||
| 217 | $results = array_merge($results, static::delimit($value, $prepend.$key.$delimiter)); |
||
| 218 | } else { |
||
| 219 | $results[$prepend.$key] = $value; |
||
| 220 | } |
||
| 221 | } |
||
| 222 | |||
| 223 | return $results; |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Alias for @see Arr::find() |
||
| 228 | */ |
||
| 229 | public static function detect(array $array, callable $callback, $default = null) |
||
| 230 | { |
||
| 231 | return static::find($array, $callback, $default); |
||
| 232 | } |
||
| 233 | |||
| 234 | /** |
||
| 235 | * Divides an array into two arrays - the first containing the keys, the second containing the values. |
||
| 236 | * |
||
| 237 | * @param array $array The initial array. |
||
| 238 | * @return array The resulting array. |
||
| 239 | */ |
||
| 240 | public static function divide(array $array) |
||
| 241 | { |
||
| 242 | return [array_keys($array), array_values($array)]; |
||
| 243 | } |
||
| 244 | |||
| 245 | /** |
||
| 246 | * Alias for @see Arr::all() |
||
| 247 | */ |
||
| 248 | public static function every(array $array, callable $callback) |
||
| 249 | { |
||
| 250 | return static::all($array, $callback); |
||
| 251 | } |
||
| 252 | |||
| 253 | /** |
||
| 254 | * Returns a subset of the given array, containing all keys except for the ones specified. |
||
| 255 | * |
||
| 256 | * @param array $array The initial array. |
||
| 257 | * @param array $keys An array of keys to exclude from the initial array. |
||
| 258 | * @return array |
||
| 259 | */ |
||
| 260 | public static function except(array $array, array $keys) |
||
| 261 | { |
||
| 262 | return array_diff_key($array, array_flip($keys)); |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * Fetches a flattened array of an element nested in the initial array. |
||
| 267 | * |
||
| 268 | * @param array $array The initial array. |
||
| 269 | * @param string $key The string delimited key. |
||
| 270 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 271 | * @return array The resulting array. |
||
| 272 | */ |
||
| 273 | public static function fetch(array $array, string $key, string $delimiter = null) : array |
||
| 274 | { |
||
| 275 | // Results holder. |
||
| 276 | $results = []; |
||
| 277 | |||
| 278 | // Which string delimiter should we use? |
||
| 279 | if (null === $delimiter) { |
||
| 280 | $delimiter = static::$delimiter; |
||
| 281 | } |
||
| 282 | |||
| 283 | foreach (explode($delimiter, $key) as $segment) { |
||
| 284 | $results = []; |
||
| 285 | |||
| 286 | foreach ($array as $value) { |
||
| 287 | $value = (array) $value; |
||
| 288 | |||
| 289 | $results[] = $value[$segment]; |
||
| 290 | } |
||
| 291 | |||
| 292 | $array = array_values($results); |
||
| 293 | } |
||
| 294 | |||
| 295 | return array_values($results); |
||
| 296 | } |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Returns the first value which passes the given truth test. |
||
| 300 | * |
||
| 301 | * Aliases: |
||
| 302 | * - @see Arr::detect() |
||
| 303 | * |
||
| 304 | * @param array $array The array to traverse. |
||
| 305 | * @param callable $callback The truth test the value should pass. |
||
| 306 | * @param mixed $default The default value to be returned if none of the elements passes the test. |
||
| 307 | * @return mixed |
||
| 308 | */ |
||
| 309 | public static function find(array $array, callable $callback, $default = null) |
||
| 310 | { |
||
| 311 | foreach ($array as $key => $value) { |
||
| 312 | if ($callback($value, $key)) { |
||
| 313 | return $value; |
||
| 314 | } |
||
| 315 | } |
||
| 316 | |||
| 317 | return $default; |
||
| 318 | } |
||
| 319 | |||
| 320 | /** |
||
| 321 | * Returns the first element of the array, |
||
| 322 | * OR the first $elements of the array when $elements is a positive integer, |
||
| 323 | * OR the first element which passes the given truth test when $elements is a callable. |
||
| 324 | * |
||
| 325 | * Aliases: @see \nyx\utils\Arr::head(), \nyx\utils\Arr::take() |
||
| 326 | * Opposite: @see \nyx\utils\Arr::last() |
||
| 327 | * |
||
| 328 | * @param array $array The array to traverse. |
||
| 329 | * @param callable|int $elements The truth test the value should pass or an integer denoting how many |
||
| 330 | * of the initial elements of the array should be returned. |
||
| 331 | * When not given, the method will return the first element of the array. |
||
| 332 | * @param mixed $default The default value to be returned if none of the elements passes |
||
| 333 | * the test or the array is empty. |
||
| 334 | * @throws \InvalidArgumentException When $elements is an integer smaller than 1. |
||
| 335 | * @throws \InvalidArgumentException When $elements is neither a valid integer nor a callable. |
||
| 336 | * @return mixed |
||
| 337 | */ |
||
| 338 | View Code Duplication | public static function first(array $array, $elements = null, $default = null) |
|
| 339 | { |
||
| 340 | if (empty($array)) { |
||
| 341 | return $default; |
||
| 342 | } |
||
| 343 | |||
| 344 | // Most common use case - simply return the first value of the array. |
||
| 345 | if (!isset($elements) || $elements === 1) { |
||
| 346 | return reset($array); |
||
| 347 | } |
||
| 348 | |||
| 349 | // With a integer given, return a slice containing the first $callback elements. |
||
| 350 | if (is_int($elements)) { |
||
| 351 | |||
| 352 | if ($elements < 1) { |
||
| 353 | throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested."); |
||
| 354 | } |
||
| 355 | |||
| 356 | return array_slice($array, 0, $elements); |
||
| 357 | } |
||
| 358 | |||
| 359 | // With a callable given, return the first value which passes the given truth test. |
||
| 360 | if (is_callable($elements)) { |
||
| 361 | return static::find($array, $elements, $default); |
||
| 362 | } |
||
| 363 | |||
| 364 | throw new \InvalidArgumentException('Expected $callback to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.'); |
||
| 365 | } |
||
| 366 | |||
| 367 | /** |
||
| 368 | * Flattens a multi-dimensional array. |
||
| 369 | * |
||
| 370 | * @param array $array The initial array. |
||
| 371 | * @return array The flattened array. |
||
| 372 | */ |
||
| 373 | public static function flatten(array $array) |
||
| 374 | { |
||
| 375 | $results = []; |
||
| 376 | |||
| 377 | array_walk_recursive($array, function($x) use (&$results) { |
||
| 378 | $results[] = $x; |
||
| 379 | }); |
||
| 380 | |||
| 381 | return $results; |
||
| 382 | } |
||
| 383 | |||
| 384 | /** |
||
| 385 | * Returns a string delimited key from an array, with a default value if the given key does not exist. If null |
||
| 386 | * is given instead of a key, the whole initial array will be returned. |
||
| 387 | * |
||
| 388 | * Note: Nested objects will be accessed as if they were arrays, eg. if "some.nested.object" is an object, |
||
| 389 | * then looking for "some.nested.object.property" will be handled just as a normal array would. |
||
| 390 | * |
||
| 391 | * @param array $array The array to search in. |
||
| 392 | * @param string|array $key The string delimited key or a chain (array) of nested keys pointing |
||
| 393 | * to the desired key. |
||
| 394 | * @param mixed $default The default value. |
||
| 395 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 396 | * @return mixed |
||
| 397 | */ |
||
| 398 | public static function get(array $array, $key = null, $default = null, string $delimiter = null) |
||
| 399 | { |
||
| 400 | // Make loops easier for the end-user - return the initial array if the key is null instead of forcing |
||
| 401 | // a valid value. |
||
| 402 | if (!isset($key)) { |
||
| 403 | return $array; |
||
| 404 | } |
||
| 405 | |||
| 406 | // Which string delimiter should we use? |
||
| 407 | if (!isset($delimiter)) { |
||
| 408 | $delimiter = static::$delimiter; |
||
| 409 | } |
||
| 410 | |||
| 411 | // If the key is string delimited, we need to explode it into an array of segments. |
||
| 412 | $segments = is_array($key) ? $key : explode($delimiter, $key); |
||
| 413 | |||
| 414 | // One dimension at a time. |
||
| 415 | while ($segment = array_shift($segments)) { |
||
| 416 | |||
| 417 | // If the current segment is a wildcard, make sure the it points to an array |
||
| 418 | // and pluck the remaining segments from it. |
||
| 419 | if ($segment === '*') { |
||
| 420 | return is_array($array) ? static::pluck($array, $segments, $delimiter) : $default; |
||
| 421 | } |
||
| 422 | |||
| 423 | // Note: isset() is the cheapest condition to check for while being rather probable at the same time, |
||
| 424 | // thus the seemingly unintuitive condition ordering. |
||
| 425 | if (isset($array->{$segment})) { |
||
| 426 | $array = $array->{$segment}; |
||
| 427 | } elseif (is_array($array) && array_key_exists($segment, $array)) { |
||
| 428 | $array = $array[$segment]; |
||
| 429 | } else { |
||
| 430 | return $default; |
||
| 431 | } |
||
| 432 | } |
||
| 433 | |||
| 434 | return $array; |
||
| 435 | } |
||
| 436 | |||
| 437 | /** |
||
| 438 | * Checks whether the given string delimited key exists in the array. |
||
| 439 | * |
||
| 440 | * Note: Nested objects will not be accessed as if they were arrays, ie. if "some.nested.object" is an object, |
||
| 441 | * not an array, then looking for "some.nested.object.key" will always return false. |
||
| 442 | * |
||
| 443 | * @param array $array The array to search in. |
||
| 444 | * @param string $key The string delimited key. |
||
| 445 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 446 | * @return bool True when the given key exists, false otherwise. |
||
| 447 | */ |
||
| 448 | public static function has(array $array, string $key, string $delimiter = null) |
||
| 449 | { |
||
| 450 | // Which string delimiter should we use? |
||
| 451 | if (null === $delimiter) { |
||
| 452 | $delimiter = static::$delimiter; |
||
| 453 | } |
||
| 454 | |||
| 455 | foreach (explode($delimiter, $key) as $segment) { |
||
| 456 | if (!is_array($array) || !array_key_exists($segment, $array)) { |
||
| 457 | return false; |
||
| 458 | } |
||
| 459 | |||
| 460 | $array = $array[$segment]; |
||
| 461 | } |
||
| 462 | |||
| 463 | return true; |
||
| 464 | } |
||
| 465 | |||
| 466 | /** |
||
| 467 | * @see \nyx\utils\Arr::first() |
||
| 468 | */ |
||
| 469 | public static function head(array $array, $callback = null, $default = null) |
||
| 470 | { |
||
| 471 | return static::first($array, $callback, $default); |
||
| 472 | } |
||
| 473 | |||
| 474 | /** |
||
| 475 | * Returns all but the last value(s) of the given array. |
||
| 476 | * |
||
| 477 | * If a callable is passed, elements at the end of the array are excluded from the result as long as the |
||
| 478 | * callback returns a truthy value. If a number is passed, the last n values are excluded from the result. |
||
| 479 | * |
||
| 480 | * @param array $array The array to traverse. |
||
| 481 | * @param callable|int $callback The truth test the value should pass or an integer denoting how many |
||
| 482 | * of the final elements of the array should be excluded. The count is |
||
| 483 | * 1-indexed, ie. if you want to exclude the last 2 elements, pass 2. |
||
| 484 | * @param mixed $default The default value to be returned if none of the elements passes the test. |
||
| 485 | * Only useful when $callback is a callable. |
||
| 486 | * @return mixed |
||
| 487 | */ |
||
| 488 | public static function initial(array $array, $callback, $default = null) |
||
| 489 | { |
||
| 490 | // When given a callable, keep counting as long as the callable returns a truthy value. |
||
| 491 | View Code Duplication | if (is_callable($callback)) { |
|
| 492 | $i = 0; |
||
| 493 | |||
| 494 | foreach (array_reverse($array) as $key => $value) { |
||
| 495 | if (!$callback($value, $key)) { |
||
| 496 | break; |
||
| 497 | } |
||
| 498 | |||
| 499 | $i++; |
||
| 500 | } |
||
| 501 | |||
| 502 | // If we didn't get at least a single truthy value, return the default. |
||
| 503 | if ($i === 0) { |
||
| 504 | return $default; |
||
| 505 | } |
||
| 506 | |||
| 507 | // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the |
||
| 508 | // first place. |
||
| 509 | $callback = $i; |
||
| 510 | } |
||
| 511 | |||
| 512 | // At this point we need a positive integer, 1 at minimum. |
||
| 513 | return array_slice($array, 0, -(int) (!$callback ? 1 : abs($callback))); |
||
| 514 | } |
||
| 515 | |||
| 516 | /** |
||
| 517 | * Checks whether the given array is an associative array. |
||
| 518 | * |
||
| 519 | * @param array $array The array to check. |
||
| 520 | * @return bool True when the array is associative, false otherwise. |
||
| 521 | */ |
||
| 522 | public static function isAssociative(array $array) : bool |
||
| 523 | { |
||
| 524 | return array_keys($array) !== range(0, count($array) - 1); |
||
| 525 | } |
||
| 526 | |||
| 527 | /** |
||
| 528 | * Checks whether the given array is a multidimensional array. |
||
| 529 | * |
||
| 530 | * @param array $array The array to check. |
||
| 531 | * @return bool True when the array has multiple dimensions, false otherwise. |
||
| 532 | */ |
||
| 533 | public static function isMultidimensional(array $array) : bool |
||
| 534 | { |
||
| 535 | return count($array) !== count($array, COUNT_RECURSIVE); |
||
| 536 | } |
||
| 537 | |||
| 538 | /** |
||
| 539 | * Returns the last element of the array, |
||
| 540 | * OR the last $elements of the array when $elements is a positive integer, |
||
| 541 | * OR the last element which passes the given truth test when $elements is a callable. |
||
| 542 | * |
||
| 543 | * Opposite: @see \nyx\utils\Arr::first() |
||
| 544 | * |
||
| 545 | * @param array $array The array to traverse. |
||
| 546 | * @param callable|int $elements The truth test the value should pass or a positive integer |
||
| 547 | * denoting how many of the final elements of the array should be returned. |
||
| 548 | * When not given, the method will return the first element of the array. |
||
| 549 | * @param mixed $default The default value to be returned if none of the elements passes |
||
| 550 | * the test or the array is empty. |
||
| 551 | * @throws \InvalidArgumentException When $elements is an integer smaller than 1. |
||
| 552 | * @throws \InvalidArgumentException When $elements is neither a valid integer nor a callable. |
||
| 553 | * @return mixed |
||
| 554 | */ |
||
| 555 | View Code Duplication | public static function last(array $array, $elements = null, $default = null) |
|
| 556 | { |
||
| 557 | if (empty($array)) { |
||
| 558 | return $default; |
||
| 559 | } |
||
| 560 | |||
| 561 | // Most common use case - simply return the last value of the array. |
||
| 562 | if (!isset($elements) || $elements === 1) { |
||
| 563 | return end($array); |
||
| 564 | } |
||
| 565 | |||
| 566 | // With a integer given, return a slice containing the last $elements elements. |
||
| 567 | if (is_int($elements)) { |
||
| 568 | |||
| 569 | if ($elements < 1) { |
||
| 570 | throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested."); |
||
| 571 | } |
||
| 572 | |||
| 573 | return array_slice($array, -$elements); |
||
| 574 | } |
||
| 575 | |||
| 576 | // With a callable given, return the last value which passes the given truth test. |
||
| 577 | if (is_callable($elements)) { |
||
| 578 | return static::find(array_reverse($array), $elements, $default); |
||
| 579 | } |
||
| 580 | |||
| 581 | throw new \InvalidArgumentException('Expected $elements to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.'); |
||
| 582 | } |
||
| 583 | |||
| 584 | /** |
||
| 585 | * Returns the biggest value from the given array. |
||
| 586 | * |
||
| 587 | * @param array $array The array to traverse. |
||
| 588 | * @return mixed The resulting value. |
||
| 589 | */ |
||
| 590 | public static function max(array $array) |
||
| 591 | { |
||
| 592 | // Avoid some overhead at this point already if possible. |
||
| 593 | if (empty($array)) { |
||
| 594 | return null; |
||
| 595 | } |
||
| 596 | |||
| 597 | // Sort in a descending order. |
||
| 598 | arsort($array); |
||
| 599 | |||
| 600 | // Return the first element of the sorted array. |
||
| 601 | return reset($array); |
||
| 602 | } |
||
| 603 | |||
| 604 | /** |
||
| 605 | * Merges 2 or more arrays recursively. Differs in two important aspects from array_merge_recursive(), to |
||
| 606 | * more closely resemble the standard behaviour of the non-recursive array_merge(): |
||
| 607 | * - In case of 2 different values, when they are not arrays, the latter one overwrites the earlier instead |
||
| 608 | * of merging them into an array; |
||
| 609 | * - Non-conflicting numeric keys are left unchanged. In case of a conflict, the new value will be appended |
||
| 610 | * to the resulting array (not preserving its key). |
||
| 611 | * |
||
| 612 | * @param array $array The initial array. |
||
| 613 | * @param array ...$with One or more (ie. separate arguments) arrays to merge in. |
||
| 614 | * @return array The resulting merged array. |
||
| 615 | */ |
||
| 616 | public static function merge(array $array, array ...$with) : array |
||
| 617 | { |
||
| 618 | foreach ($with as $arr) { |
||
| 619 | foreach ($arr as $key => $value) { |
||
| 620 | // Append numeric keys. |
||
| 621 | if (is_int($key)) { |
||
| 622 | array_key_exists($key, $array) ? $array[] = $value : $array[$key] = $value; |
||
| 623 | } |
||
| 624 | // Merge multi-dimensional arrays recursively. |
||
| 625 | elseif (is_array($value) && array_key_exists($key, $array) && is_array($array[$key])) { |
||
| 626 | $array[$key] = static::merge($array[$key], $value); |
||
| 627 | } else { |
||
| 628 | $array[$key] = $value; |
||
| 629 | } |
||
| 630 | } |
||
| 631 | } |
||
| 632 | |||
| 633 | return $array; |
||
| 634 | } |
||
| 635 | |||
| 636 | /** |
||
| 637 | * Returns the smallest value from the given array. |
||
| 638 | * |
||
| 639 | * @param array $array The array to traverse. |
||
| 640 | * @return mixed The resulting value. |
||
| 641 | */ |
||
| 642 | public static function min(array $array) |
||
| 643 | { |
||
| 644 | // Avoid some overhead at this point already if possible. |
||
| 645 | if (empty($array)) { |
||
| 646 | return null; |
||
| 647 | } |
||
| 648 | |||
| 649 | // Sort in an ascending order. |
||
| 650 | asort($array); |
||
| 651 | |||
| 652 | // Return the first element of the sorted array. |
||
| 653 | return reset($array); |
||
| 654 | } |
||
| 655 | |||
| 656 | /** |
||
| 657 | * Returns a subset of the given array, containing only the specified keys. |
||
| 658 | * |
||
| 659 | * @param array $array The initial array. |
||
| 660 | * @param array $keys An array of keys (the keys are expected to be values of this array). |
||
| 661 | * @return array |
||
| 662 | */ |
||
| 663 | public static function only(array $array, array $keys) : array |
||
| 664 | { |
||
| 665 | return array_intersect_key($array, array_values($keys)); |
||
| 666 | } |
||
| 667 | |||
| 668 | /** |
||
| 669 | * Returns a random value from the given array, or $elements random values when $elements is a positive integer, or |
||
| 670 | * the first random element that passes the given truth test when $elements is a callable. |
||
| 671 | * |
||
| 672 | * @param array $array The array to search in. |
||
| 673 | * @param callable|int $elements The number of random values to return or a callable to return the first |
||
| 674 | * randomly shuffled element that passes the given truth test. |
||
| 675 | * @param mixed $default The default value to be returned if none of the elements passes |
||
| 676 | * the test or the array is empty. |
||
| 677 | * @return mixed |
||
| 678 | */ |
||
| 679 | public static function pick(array $array, $elements = null, $default = null) |
||
| 680 | { |
||
| 681 | // There are *slightly* better performing alternatives, but simply shuffling and delegating |
||
| 682 | // the actual picking to static::first() allows us to avoid a fair amount of duplicated code. |
||
| 683 | shuffle($array); |
||
| 684 | |||
| 685 | return static::first($array, $elements, $default); |
||
| 686 | } |
||
| 687 | |||
| 688 | /** |
||
| 689 | * Given an array containing other arrays or objects, looks for the value with the given key/property |
||
| 690 | * of $key within them and returns a new array containing all values of said key from the initial array. |
||
| 691 | * Essentially like fetching a single column from a classic database table. |
||
| 692 | * |
||
| 693 | * When the optional $index parameter is given, the resulting array will be indexed by the values corresponding |
||
| 694 | * to the given $index. |
||
| 695 | * |
||
| 696 | * @see array_column() A faster and simpler alternative, if you do not need to pluck data with support |
||
| 697 | * for delimited keys or wildcards. |
||
| 698 | * |
||
| 699 | * @param array $array The array to search in. |
||
| 700 | * @param string|array $key The key of the value to look for. |
||
| 701 | * @param string|array $index The key of the value to index the resulting array by. |
||
| 702 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 703 | * @return array |
||
| 704 | */ |
||
| 705 | public static function pluck(array $array, $key, $index = null, string $delimiter = null) : array |
||
| 706 | { |
||
| 707 | $results = []; |
||
| 708 | |||
| 709 | // Which string delimiter should we use? |
||
| 710 | if (!isset($delimiter)) { |
||
| 711 | $delimiter = static::$delimiter; |
||
| 712 | } |
||
| 713 | |||
| 714 | foreach ($array as $item) { |
||
| 715 | |||
| 716 | $itemValue = static::get($item, $key, $delimiter); |
||
| 717 | |||
| 718 | // If the key given is null, the resulting array will contain numerically indexed keys. |
||
| 719 | if (!isset($index)) { |
||
| 720 | $results[] = $itemValue; |
||
| 721 | } |
||
| 722 | // Otherwise we are going use the value of the given key and use it in the resulting array as key |
||
| 723 | // for the value determined earlier. |
||
| 724 | else { |
||
| 725 | $results[static::get($item, $index, $delimiter)] = $itemValue; |
||
| 726 | } |
||
| 727 | } |
||
| 728 | |||
| 729 | return $results; |
||
| 730 | } |
||
| 731 | |||
| 732 | /** |
||
| 733 | * Returns the value for a string delimited key from an array and then removes it. |
||
| 734 | * |
||
| 735 | * @param array $array The array to search in. |
||
| 736 | * @param string $key The string delimited key. |
||
| 737 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 738 | * @return mixed |
||
| 739 | */ |
||
| 740 | public static function pull(&$array, string $key, string $delimiter = null) |
||
| 741 | { |
||
| 742 | $value = static::get($array, $key, null, $delimiter); |
||
| 743 | |||
| 744 | static::remove($array, $key, $delimiter); |
||
| 745 | |||
| 746 | return $value; |
||
| 747 | } |
||
| 748 | |||
| 749 | /** |
||
| 750 | * Removes a string delimited key from the given array. |
||
| 751 | * |
||
| 752 | * @param array& $array The array to search in. |
||
|
0 ignored issues
–
show
|
|||
| 753 | * @param string $key The string delimited key. |
||
| 754 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 755 | */ |
||
| 756 | public static function remove(array& $array, string $key, string $delimiter = null) |
||
| 757 | { |
||
| 758 | // Which string delimiter should we use? |
||
| 759 | if (!isset($delimiter)) { |
||
| 760 | $delimiter = static::$delimiter; |
||
| 761 | } |
||
| 762 | |||
| 763 | // Explode the key according to that delimiter. |
||
| 764 | $keys = explode($delimiter, $key); |
||
| 765 | |||
| 766 | while ($key = array_shift($keys)) { |
||
| 767 | if (!isset($array[$key]) || !is_array($array[$key])) { |
||
| 768 | return; |
||
| 769 | } |
||
| 770 | |||
| 771 | $array =& $array[$key]; |
||
| 772 | } |
||
| 773 | |||
| 774 | unset($array[array_shift($keys)]); |
||
| 775 | } |
||
| 776 | |||
| 777 | /** |
||
| 778 | * Returns all but the first value of the given array, all but the first elements for which the $callback |
||
| 779 | * returns true if $callback is a callable, or all but the first $callback elements if $callback is a number. |
||
| 780 | * |
||
| 781 | * Aliases: |
||
| 782 | * - @see Arr::tail() |
||
| 783 | * |
||
| 784 | * @param array $array The array to traverse. |
||
| 785 | * @param callable|int|bool $callback The truth test the value should pass or an integer denoting how many |
||
| 786 | * of the initial elements of the array should be excluded. The count |
||
| 787 | * is 1-indexed, ie. if you want to exclude the first 2 elements, pass 2. |
||
| 788 | * When a falsy value is given, the method will return all but the first |
||
| 789 | * element of the array. |
||
| 790 | * @param mixed $default The default value to be returned if none of the elements passes the |
||
| 791 | * test or the array contains no more than one item. |
||
| 792 | * @return mixed |
||
| 793 | */ |
||
| 794 | public static function rest(array $array, $callback = false, $default = null) |
||
| 795 | { |
||
| 796 | // Avoid some overhead at this point already if possible. We need at least 2 elements in the array for |
||
| 797 | // this method to make any usage sense. |
||
| 798 | if (2 > count($array)) { |
||
| 799 | return $default; |
||
| 800 | } |
||
| 801 | |||
| 802 | // For a falsy callback, return all but the first element of the array. |
||
| 803 | if (!$callback) { |
||
| 804 | return array_slice($array, 1); |
||
| 805 | } |
||
| 806 | |||
| 807 | // With a callable given, keep counting as long as the callable returns a truthy value. |
||
| 808 | View Code Duplication | if (is_callable($callback)) { |
|
| 809 | $i = 0; |
||
| 810 | |||
| 811 | foreach ($array as $key => $value) { |
||
| 812 | if (!$callback($value, $key)) { |
||
| 813 | break; |
||
| 814 | } |
||
| 815 | |||
| 816 | $i++; |
||
| 817 | } |
||
| 818 | |||
| 819 | // If we didn't get at least a single truthy value, return the default. |
||
| 820 | if ($i === 0) { |
||
| 821 | return $default; |
||
| 822 | } |
||
| 823 | |||
| 824 | // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the |
||
| 825 | // first place. |
||
| 826 | $callback = $i; |
||
| 827 | } |
||
| 828 | |||
| 829 | // Return the final $callback elements. |
||
| 830 | return array_slice($array, abs((int) $callback)); |
||
| 831 | } |
||
| 832 | |||
| 833 | /** |
||
| 834 | * Sets the given value for a string delimited key within the given array. If null is given instead of a key, |
||
| 835 | * the whole initial array will be overwritten with the given value. |
||
| 836 | * |
||
| 837 | * @param array $array The array to set the value in. |
||
| 838 | * @param string $key The string delimited key. |
||
| 839 | * @param mixed $value The value to set. |
||
| 840 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
| 841 | * @return mixed |
||
| 842 | */ |
||
| 843 | public static function set(array& $array, $key, $value, string $delimiter = null) |
||
| 844 | { |
||
| 845 | // Make loops easier for the end-user - overwrite the whole array if the key is null. |
||
| 846 | if (null === $key) { |
||
| 847 | return $array = $value; |
||
| 848 | } |
||
| 849 | |||
| 850 | // Which string delimiter should we use? |
||
| 851 | if (null === $delimiter) { |
||
| 852 | $delimiter = static::$delimiter; |
||
| 853 | } |
||
| 854 | |||
| 855 | // Explode the key according to that delimiter. |
||
| 856 | $keys = explode($delimiter, $key); |
||
| 857 | |||
| 858 | while (count($keys) > 1) { |
||
| 859 | $key = array_shift($keys); |
||
| 860 | |||
| 861 | if (!isset($array[$key]) || !is_array($array[$key])) { |
||
| 862 | $array[$key] = []; |
||
| 863 | } |
||
| 864 | |||
| 865 | $array =& $array[$key]; |
||
| 866 | } |
||
| 867 | |||
| 868 | return $array[array_shift($keys)] = $value; |
||
| 869 | } |
||
| 870 | |||
| 871 | /** |
||
| 872 | * Alias for {@see static::any()} |
||
| 873 | */ |
||
| 874 | public static function some(array $array, callable $callback) : bool |
||
| 875 | { |
||
| 876 | return static::any($array, $callback); |
||
| 877 | } |
||
| 878 | |||
| 879 | /** |
||
| 880 | * Alias for {@see static::rest()} |
||
| 881 | */ |
||
| 882 | public static function tail(array $array, $callback = false, $default = null) |
||
| 883 | { |
||
| 884 | return static::rest($array, $callback, $default); |
||
| 885 | } |
||
| 886 | |||
| 887 | /** |
||
| 888 | * @see \nyx\utils\Arr::first() |
||
| 889 | */ |
||
| 890 | public static function take(array $array, $callback = null, $default = null) |
||
| 891 | { |
||
| 892 | return static::first($array, $callback, $default); |
||
| 893 | } |
||
| 894 | |||
| 895 | /** |
||
| 896 | * Returns an array based on the initial array with all occurrences of the passed values removed. Uses strict |
||
| 897 | * equality comparisons. |
||
| 898 | * |
||
| 899 | * @param array $array The array to traverse. |
||
| 900 | * @param mixed ...$values The values which should get removed. |
||
| 901 | * @return array |
||
| 902 | */ |
||
| 903 | public static function without(array $array, ...$values) : array |
||
| 904 | { |
||
| 905 | return array_filter($array, function($value) use ($values) { |
||
| 906 | return !in_array($value, $values, true); |
||
| 907 | }); |
||
| 908 | } |
||
| 909 | } |
||
| 910 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.