: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @license https://opensource.org/licenses/mit-license.php MIT License
use InvalidArgumentException;
* Library of array functions for manipulating and extracting data
* from arrays or 'sets' of data.
* `Hash` provides an improved interface, more consistent and
* predictable set of features over `Set`. While it lacks the spotty
* support for pseudo Xpath, its more fully featured dot notation provides
* similar features in a more consistent implementation.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html
* Get a single value specified by $path out of $data.
* Does not support the full dot notation feature set,
* but is faster for simple read operations.
* @param array|\ArrayAccess $data Array of data or object implementing
* \ArrayAccess interface to operate on.
* @param string|int|string[]|null $path The path being searched for. Either a dot
* separated string, or an array of path segments.
* @param mixed $default The return value when the path does not exist
* @throws \InvalidArgumentException
* @return mixed The value fetched from the array, or null.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::get
public static function get($data, $path, $default = null)
if (!(is_array($data) || $data instanceof ArrayAccess)) {
throw new InvalidArgumentException(
'Invalid data type, must be an array or \ArrayAccess instance.'
if (empty($data) || $path === null) {
if (is_string($path) || is_numeric($path)) {
$parts = explode('.', (string)$path);
throw new InvalidArgumentException(sprintf(
'Invalid Parameter %s, should be dot separated path or array.',
return isset($data[$parts[0]]) ? $data[$parts[0]] : $default;
return isset($data[$parts[0]][$parts[1]]) ? $data[$parts[0]][$parts[1]] : $default;
return isset($data[$parts[0]][$parts[1]][$parts[2]]) ? $data[$parts[0]][$parts[1]][$parts[2]] : $default;
foreach ($parts as $key) {
if ((is_array($data) || $data instanceof ArrayAccess) && isset($data[$key])) {
* Gets the values from an array matching the $path expression.
* The path expression is a dot separated expression, that can contain a set
* of patterns and expressions:
* - `{n}` Matches any numeric key, or integer.
* - `{s}` Matches any string key.
* - `{*}` Matches any value.
* - `Foo` Matches any key with the exact same value.
* There are a number of attribute operators:
* - `>`, `<`, `>=`, `<=` Value comparison.
* - `=/.../` Regular expression pattern match.
* Given a set of User array data, from a `$usersTable->find('all')` call:
* - `1.User.name` Get the name of the user at index 1.
* - `{n}.User.name` Get the name of every user in the set of users.
* - `{n}.User[id].name` Get the name of every user with an id key.
* - `{n}.User[id>=2].name` Get the name of every user with an id key greater than or equal to 2.
* - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`.
* - `{n}.User[id=1].name` Get the Users name with id matching `1`.
* @param array|\ArrayAccess $data The data to extract from.
* @param string $path The path to extract.
* @return array|\ArrayAccess An array of the extracted values. Returns an empty array
* if there are no matches.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::extract
public static function extract($data, $path)
if (!(is_array($data) || $data instanceof ArrayAccess)) {
throw new InvalidArgumentException(
'Invalid data type, must be an array or \ArrayAccess instance.'
if (!preg_match('/[{\[]/', $path)) {
$data = static::get($data, $path);
if ($data !== null && !(is_array($data) || $data instanceof ArrayAccess)) {
return $data !== null ? (array)$data : [];
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
$tokens = Text::tokenize($path, '.', '[', ']');
$context = [$_key => [$data]];
foreach ($tokens as $token) {
list($token, $conditions) = self::_splitConditions($token);
foreach ($context[$_key] as $item) {
if (is_object($item) && method_exists($item, 'toArray')) {
/** @var \Cake\Datasource\EntityInterface $item */
$item = $item->toArray();
foreach ((array)$item as $k => $v) {
if (static::_matchToken($k, $token)) {
// Filter for attributes.
foreach ($next as $item) {
(is_array($item) || $item instanceof ArrayAccess) &&
static::_matches($item, $conditions)
$context = [$_key => $next];
* @param string $token the token being splitted.
* @return array [token, conditions] with token splitted
protected static function _splitConditions($token)
$position = strpos($token, '[');
if ($position !== false) {
$conditions = substr($token, $position);
$token = substr($token, 0, $position);
return [$token, $conditions];
* Check a key against a token.
* @param string $key The key in the array being searched.
* @param string $token The token being matched.
protected static function _matchToken($key, $token)
return is_numeric($token) ? ($key == $token) : $key === $token;
* Checks whether or not $data matches the attribute patterns
* @param array|\ArrayAccess $data Array of data to match.
* @param string $selector The patterns to match.
* @return bool Fitness of expression.
protected static function _matches($data, $selector)
'/(\[ (?P<attr>[^=><!]+?) (\s* (?P<op>[><!]?[=]|[><]) \s* (?P<val>(?:\/.*?\/ | [^\]]+)) )? \])/x',
foreach ($conditions as $cond) {
$op = isset($cond['op']) ? $cond['op'] : null;
$val = isset($cond['val']) ? $cond['val'] : null;
if (empty($op) && empty($val) && !isset($data[$attr])) {
$attrPresent = array_key_exists($attr, $data);
$attrPresent = $data->offsetExists($attr);
// Empty attribute = fail.
if (isset($data[$attr])) {
$isBool = is_bool($prop);
if ($isBool && is_numeric($val)) {
$prop = $prop ? '1' : '0';
$prop = $prop ? 'true' : 'false';
} elseif (is_numeric($prop)) {
// Pattern matches and other operators.
if ($op === '=' && $val && $val[0] === '/') {
if (!preg_match($val, $prop)) {
($op === '=' && $prop != $val) ||
($op === '!=' && $prop == $val) ||
($op === '>' && $prop <= $val) ||
($op === '<' && $prop >= $val) ||
($op === '>=' && $prop < $val) ||
($op === '<=' && $prop > $val)
* Insert $values into an array with the given $path. You can use
* `{n}` and `{s}` elements to insert $data multiple times.
* @param array $data The data to insert into.
* @param string $path The path to insert at.
* @param mixed $values The values to insert.
* @return array The data with $values inserted.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::insert
public static function insert(array $data, $path, $values = null)
$noTokens = strpos($path, '[') === false;
if ($noTokens && strpos($path, '.') === false) {
$tokens = explode('.', $path);
$tokens = Text::tokenize($path, '.', '[', ']');
if ($noTokens && strpos($path, '{') === false) {
return static::_simpleOp('insert', $data, $tokens, $values);
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = static::_splitConditions($token);
foreach ($data as $k => $v) {
if (static::_matchToken($k, $token)) {
if (!$conditions || static::_matches($v, $conditions)) {
? static::insert($v, $nextPath, $values)
: array_merge($v, (array)$values);
* Perform a simple insert/remove operation.
* @param string $op The operation to do.
* @param array $data The data to operate on.
* @param string[] $path The path to work on.
* @param mixed $values The values to insert when doing inserts.
protected static function _simpleOp($op, $data, $path, $values = null)
foreach ($path as $i => $key) {
if (!isset($_list[$key])) {
} elseif ($op === 'remove') {
if (!isset($_list[$key])) {
* Remove data matching $path from the $data array.
* You can use `{n}` and `{s}` to remove multiple elements
* @param array $data The data to operate on
* @param string $path A path expression to use to remove.
* @return array The modified array.
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::remove
public static function remove(array $data, $path)
$noTokens = strpos($path, '[') === false;
$noExpansion = strpos($path, '{') === false;
if ($noExpansion && $noTokens && strpos($path, '.') === false) {
$tokens = $noTokens ? explode('.', $path) : Text::tokenize($path, '.', '[', ']');
if ($noExpansion && $noTokens) {
return static::_simpleOp('remove', $data, $tokens);
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = self::_splitConditions($token);
foreach ($data as $k => $v) {
$match = static::_matchToken($k, $token);
if ($match && is_array($v)) {
if (static::_matches($v, $conditions)) {
$data[$k] = static::remove($v, $nextPath);
$data[$k] = static::remove($v, $nextPath);
} elseif ($match && $nextPath === '') {
* Creates an associative array using `$keyPath` as the path to build its keys, and optionally
* `$valuePath` as path to get the values. If `$valuePath` is not specified, all values will be initialized
* to null (useful for Hash::merge). You can optionally group the values by what is obtained when
* following the path specified in `$groupPath`.
* @param array $data Array from where to extract keys and values
* @param array|string|null $keyPath A dot-separated string. If null it will create a numbered array.
* @param array|string|null $valuePath A dot-separated string.
* @param string|null $groupPath A dot-separated string.
* @return array Combined array
* @link https://book.cakephp.org/3/en/core-libraries/hash.html#Cake\Utility\Hash::combine
* @throws \RuntimeException When keys is an array, and keys and values count is unequal.
public static function combine(array $data, $keyPath, $valuePath = null, $groupPath = null)
if (is_array($keyPath)) {
$format = array_shift($keyPath);
$keys = static::format($data, $keyPath, $format);
} elseif ($keyPath === null) {
$keys = static::extract($data, $keyPath);
if ($keyPath !== null && empty($keys)) {
if (!empty($valuePath) && is_array($valuePath)) {
$format = array_shift($valuePath);
$vals = static::format($data, $valuePath, $format);
} elseif (!empty($valuePath)) {
$vals = static::extract($data, $valuePath);
$vals = array_fill(0, $keys === null ? count($data) : count($keys), null);
if (is_array($keys) && count($keys) !== count($vals)) {
throw new RuntimeException(
'Hash::combine() needs an equal number of keys + values.'
if ($groupPath !== null) {
$group = static::extract($data, $groupPath);