: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$c = is_array($keys) ? count($keys) : count($vals);
for ($i = 0; $i < $c; $i++) {
if (!isset($group[$i])) {
if (!isset($out[$group[$i]])) {
$out[$group[$i]][] = $vals[$i];
$out[$group[$i]][$keys[$i]] = $vals[$i];
return array_combine($keys === null ? range(0, count($vals) - 1) : $keys, $vals);
* Returns a formatted series of values extracted from `$data`, using
* `$format` as the format and `$paths` as the values to extract.
* $result = Hash::format($users, ['{n}.User.id', '{n}.User.name'], '%s : %s');
* The `$format` string can use any format options that `vsprintf()` and `sprintf()` do.
* @param array $data Source array from which to extract the data
* @param string[] $paths An array containing one or more Hash::extract()-style key paths
* @param string $format Format string into which values will be inserted, see sprintf()
* @return string[]|null An array of strings extracted from `$path` and formatted with `$format`
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::format
* @see \Cake\Utility\Hash::extract()
public static function format(array $data, array $paths, $format)
for ($i = 0; $i < $count; $i++) {
$extracted[] = static::extract($data, $paths[$i]);
$count = count($data[0]);
$countTwo = count($data);
for ($j = 0; $j < $count; $j++) {
for ($i = 0; $i < $countTwo; $i++) {
if (array_key_exists($j, $data[$i])) {
$out[] = vsprintf($format, $args);
* Determines if one array contains the exact keys and values of another.
* @param array $data The data to search through.
* @param array $needle The values to file in $data
* @return bool true If $data contains $needle, false otherwise
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::contains
public static function contains(array $data, array $needle)
if (empty($data) || empty($needle)) {
while (!empty($needle)) {
if (array_key_exists($key, $data) && is_array($val)) {
$stack[] = [$val, $next];
} elseif (!array_key_exists($key, $data) || $data[$key] != $val) {
if (empty($needle) && !empty($stack)) {
list($needle, $data) = array_pop($stack);
* Test whether or not a given path exists in $data.
* This method uses the same path syntax as Hash::extract()
* Checking for paths that could target more than one element will
* make sure that at least one matching element exists.
* @param array $data The data to check.
* @param string $path The path to check for.
* @return bool Existence of path.
* @see \Cake\Utility\Hash::extract()
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::check
public static function check(array $data, $path)
$results = static::extract($data, $path);
if (!is_array($results)) {
return count($results) > 0;
* Recursively filters a data set.
* @param array $data Either an array to filter, or value when in callback
* @param callable|array $callback A function to filter the data with. Defaults to
* `static::_filter()` Which strips out all non-zero empty values.
* @return array Filtered array
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::filter
public static function filter(array $data, $callback = ['self', '_filter'])
foreach ($data as $k => $v) {
$data[$k] = static::filter($v, $callback);
return array_filter($data, $callback);
* Callback function for filtering.
* @param mixed $var Array to filter.
protected static function _filter($var)
return $var === 0 || $var === 0.0 || $var === '0' || !empty($var);
* Collapses a multi-dimensional array into a single dimension, using a delimited array path for
* each array element's key, i.e. [['Foo' => ['Bar' => 'Far']]] becomes
* ['0.Foo.Bar' => 'Far'].)
* @param array $data Array to flatten
* @param string $separator String used to separate array key elements in a path, defaults to '.'
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::flatten
public static function flatten(array $data, $separator = '.')
if (is_array($element) && !empty($element)) {
$stack[] = [$data, $path];
$path .= $key . $separator;
$result[$path . $key] = $element;
if (empty($data) && !empty($stack)) {
list($data, $path) = array_pop($stack);
* Expands a flat array to a nested array.
* For example, unflattens an array that was collapsed with `Hash::flatten()`
* into a multi-dimensional array. So, `['0.Foo.Bar' => 'Far']` becomes
* `[['Foo' => ['Bar' => 'Far']]]`.
* @param array $data Flattened array
* @param string $separator The delimiter used
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::expand
public static function expand(array $data, $separator = '.')
foreach ($data as $flat => $value) {
$keys = explode($separator, (string)$flat);
$keys = array_reverse($keys);
$stack = [[$child, &$result]];
static::_merge($stack, $result);
* This function can be thought of as a hybrid between PHP's `array_merge` and `array_merge_recursive`.
* The difference between this method and the built-in ones, is that if an array key contains another array, then
* Hash::merge() will behave in a recursive fashion (unlike `array_merge`). But it will not act recursively for
* keys that contain scalar values (unlike `array_merge_recursive`).
* Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays.
* @param array $data Array to be merged
* @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged
* @return array Merged array
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::merge
public static function merge(array $data, $merge)
$args = array_slice(func_get_args(), 1);
foreach ($args as &$curArg) {
$stack[] = [(array)$curArg, &$return];
static::_merge($stack, $return);
* Merge helper function to reduce duplicated code between merge() and expand().
* @param array $stack The stack of operations to work with.
* @param array $return The return value to operate on.
protected static function _merge($stack, &$return)
foreach ($stack as $curKey => &$curMerge) {
foreach ($curMerge[0] as $key => &$val) {
$isArray = is_array($curMerge[1]);
if ($isArray && !empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) {
// Recurse into the current merge data as it is an array.
$stack[] = [&$val, &$curMerge[1][$key]];
} elseif ((int)$key === $key && $isArray && isset($curMerge[1][$key])) {
$curMerge[1][$key] = $val;
* Checks to see if all the values in the array are numeric
* @param array $data The array to check.
* @return bool true if values are numeric, false otherwise
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::numeric
public static function numeric(array $data)
return $data === array_filter($data, 'is_numeric');
* Counts the dimensions of an array.
* Only considers the dimension of the first element in the array.
* If you have an un-even or heterogeneous array, consider using Hash::maxDimensions()
* to get the dimensions of the array.
* @param array $data Array to count dimensions on
* @return int The number of dimensions in $data
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::dimensions
public static function dimensions(array $data)
while ($elem = array_shift($data)) {
* Counts the dimensions of *all* array elements. Useful for finding the maximum
* number of dimensions in a mixed array.
* @param array $data Array to count dimensions on
* @return int The maximum number of dimensions in $data
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::maxDimensions
public static function maxDimensions(array $data)
if (is_array($data) && !empty($data)) {
foreach ($data as $value) {
$depth[] = static::maxDimensions($value) + 1;
return empty($depth) ? 0 : max($depth);
* Map a callback across all elements in a set.
* Can be provided a path to only modify slices of the set.
* @param array $data The data to map over, and extract data out of.
* @param string $path The path to extract for mapping over.
* @param callable $function The function to call on each extracted value.
* @return array An array of the modified values.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::map
public static function map(array $data, $path, $function)
$values = (array)static::extract($data, $path);
return array_map($function, $values);
* Reduce a set of extracted values using `$function`.
* @param array $data The data to reduce.
* @param string $path The path to extract from $data.
* @param callable $function The function to call on each extracted value.
* @return mixed The reduced value.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::reduce
public static function reduce(array $data, $path, $function)
$values = (array)static::extract($data, $path);
return array_reduce($values, $function);
* Apply a callback to a set of extracted values using `$function`.
* The function will get the extracted values as the first argument.
* You can easily count the results of an extract using apply().
* For example to count the comments on an Article:
* $count = Hash::apply($data, 'Article.Comment.{n}', 'count');
* You could also use a function like `array_sum` to sum the results.
* $total = Hash::apply($data, '{n}.Item.price', 'array_sum');
* @param array $data The data to reduce.
* @param string $path The path to extract from $data.
* @param callable $function The function to call on each extracted value.
* @return mixed The results of the applied method.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::apply
public static function apply(array $data, $path, $function)
$values = (array)static::extract($data, $path);
return call_user_func($function, $values);
* Sorts an array by any value, determined by a Set-compatible path
* - `asc` Sort ascending.
* - `desc` Sort descending.
* - `regular` For regular sorting (don't change types)
* - `numeric` Compare values numerically
* - `string` Compare values as strings
* - `locale` Compare items as strings, based on the current locale
* - `natural` Compare items as strings using "natural ordering" in a human friendly way
* Will sort foo10 below foo2 as an example.
* To do case insensitive sorting, pass the type as an array as follows:
* Hash::sort($data, 'some.attribute', 'asc', ['type' => 'regular', 'ignoreCase' => true]);
* When using the array form, `type` defaults to 'regular'. The `ignoreCase` option
* @param array $data An array of data to sort
* @param string $path A Set-compatible path to the array value
* @param string $dir See directions above. Defaults to 'asc'.
* @param array|string $type See direction types above. Defaults to 'regular'.
* @return array Sorted array of data
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::sort
public static function sort(array $data, $path, $dir = 'asc', $type = 'regular')
$originalKeys = array_keys($data);
$numeric = is_numeric(implode('', $originalKeys));
$data = array_values($data);
$sortValues = static::extract($data, $path);
$dataCount = count($data);
// Make sortValues match the data length, as some keys could be missing
// the sorted value path.
$missingData = count($sortValues) < $dataCount;
if ($missingData && $numeric) {
// Get the path without the leading '{n}.'
$itemPath = substr($path, 4);
foreach ($data as $key => $value) {
$sortValues[$key] = static::get($value, $itemPath);
} elseif ($missingData) {
$sortValues = array_pad($sortValues, $dataCount, null);
$result = static::_squash($sortValues);
$keys = static::extract($result, '{n}.id');
$values = static::extract($result, '{n}.value');