Skip to content

Commit

Permalink
Use StringHelper::parsePath() instead of internal method (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
vjik authored Aug 20, 2022
1 parent 7af88eb commit 65f17be
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 201 deletions.
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# Yii Arrays Change Log


## 2.0.1 under development

- Enh #111: Add support for escaping delimiter while parsing path (@arogachev, @vjik)
- New #111: Add `ArrayHelper::parsePath()` method (@arogachev, @vjik)

## 2.0.0 October 23, 2021

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
],
"require": {
"php": "^7.4|^8.0",
"yiisoft/strings": "^2.0"
"yiisoft/strings": "^2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
Expand Down
143 changes: 40 additions & 103 deletions src/ArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use InvalidArgumentException;
use Throwable;
use Yiisoft\Strings\NumericHelper;
use Yiisoft\Strings\StringHelper;

use function array_key_exists;
use function count;
Expand All @@ -19,8 +20,6 @@
use function is_int;
use function is_object;
use function is_string;
use function sprintf;
use function strlen;

/**
* Yii array helper provides static methods allowing you to deal with arrays more efficiently.
Expand Down Expand Up @@ -300,7 +299,7 @@ public static function getValueByPath($array, $path, $default = null, string $de
{
return self::getValue(
$array,
$path instanceof Closure ? $path : self::parsePath($path, $delimiter),
$path instanceof Closure ? $path : self::parseMixedPath($path, $delimiter),
$default
);
}
Expand Down Expand Up @@ -425,104 +424,7 @@ public static function setValue(array &$array, $key, $value): void
*/
public static function setValueByPath(array &$array, $path, $value, string $delimiter = '.'): void
{
self::setValue($array, $path === null ? null : self::parsePath($path, $delimiter), $value);
}

/**
* @param array|float|int|string $path The path of where do you want to write a value to `$array`.
* The path can be described by a string when each key should be separated by delimiter. If a path item contains
* delimiter, it can be escaped with "\" (backslash) or a custom delimiter can be used.
* You can also describe the path as an array of keys.
* @param string $delimiter A separator, used to parse string key for embedded object property retrieving. Defaults
* to "." (dot).
* @param string $escapeCharacter An escape character, used to escape delimiter. Defaults to "\" (backslash).
* @param bool $preserveDelimiterEscaping Whether to preserve delimiter escaping in the items of final array (in
* case of using string as an input). When `false`, "\" (backslashes) are removed. For a "." as delimiter, "."
* becomes "\.". Defaults to `false`.
*
* @psalm-param ArrayPath $path
*
* @return array|float|int|string
* @psalm-return ArrayKey
*/
public static function parsePath(
$path,
string $delimiter = '.',
string $escapeCharacter = '\\',
bool $preserveDelimiterEscaping = false
) {
if (strlen($delimiter) !== 1) {
throw new InvalidArgumentException('Only 1 character is allowed for delimiter.');
}

if (strlen($escapeCharacter) !== 1) {
throw new InvalidArgumentException('Only 1 escape character is allowed.');
}

if ($delimiter === $escapeCharacter) {
throw new InvalidArgumentException('Delimiter and escape character must be different.');
}

if (is_array($path)) {
$newPath = [];
foreach ($path as $key) {
if (is_string($key) || is_array($key)) {
/** @var list<float|int|string> $parsedPath */
$parsedPath = self::parsePath($key, $delimiter);
$newPath = array_merge($newPath, $parsedPath);
} else {
$newPath[] = $key;
}
}
return $newPath;
}

if (!is_string($path)) {
return $path;
}

if ($path === '') {
return [];
}

$matches = preg_split(
sprintf(
'/(?<!%1$s)((?>%1$s%1$s)*)%2$s/',
preg_quote($escapeCharacter, '/'),
preg_quote($delimiter, '/')
),
$path,
-1,
PREG_SPLIT_OFFSET_CAPTURE
);
$result = [];
$countResults = count($matches);
for ($i = 1; $i < $countResults; $i++) {
$l = $matches[$i][1] - $matches[$i - 1][1] - strlen($matches[$i - 1][0]) - 1;
$result[] = $matches[$i - 1][0] . ($l > 0 ? str_repeat($escapeCharacter, $l) : '');
}
$result[] = $matches[$countResults - 1][0];

if ($preserveDelimiterEscaping === true) {
return $result;
}

return array_map(
static function (string $key) use ($delimiter, $escapeCharacter): string {
return str_replace(
[
$escapeCharacter . $escapeCharacter,
$escapeCharacter . $delimiter,
],
[
$escapeCharacter,
$delimiter,
],
$key
);
},
$result
);
self::setValue($array, $path === null ? null : self::parseMixedPath($path, $delimiter), $value);
}

/**
Expand Down Expand Up @@ -601,7 +503,7 @@ public static function remove(array &$array, $key, $default = null)
*/
public static function removeByPath(array &$array, $path, $default = null, string $delimiter = '.')
{
return self::remove($array, self::parsePath($path, $delimiter), $default);
return self::remove($array, self::parseMixedPath($path, $delimiter), $default);
}

/**
Expand Down Expand Up @@ -1023,7 +925,7 @@ public static function pathExists(
bool $caseSensitive = true,
string $delimiter = '.'
): bool {
return self::keyExists($array, self::parsePath($path, $delimiter), $caseSensitive);
return self::keyExists($array, self::parseMixedPath($path, $delimiter), $caseSensitive);
}

/**
Expand Down Expand Up @@ -1371,4 +1273,39 @@ private static function normalizeArrayKey($key): string
{
return is_float($key) ? NumericHelper::normalize($key) : (string)$key;
}

/**
* @param array|float|int|string $path
* @param string $delimiter
*
* @psalm-param ArrayPath $path
*
* @return array|float|int|string
* @psalm-return ArrayKey
*/
private static function parseMixedPath($path, string $delimiter)
{
if (is_array($path)) {
$newPath = [];
foreach ($path as $key) {
if (is_string($key)) {
$parsedPath = StringHelper::parsePath($key, $delimiter);
$newPath = array_merge($newPath, $parsedPath);
continue;
}

if (is_array($key)) {
/** @var list<float|int|string> $parsedPath */
$parsedPath = self::parseMixedPath($key, $delimiter);
$newPath = array_merge($newPath, $parsedPath);
continue;
}

$newPath[] = $key;
}
return $newPath;
}

return is_string($path) ? StringHelper::parsePath($path, $delimiter) : $path;
}
}
95 changes: 0 additions & 95 deletions tests/ArrayHelper/ParsePathTest.php

This file was deleted.

0 comments on commit 65f17be

Please sign in to comment.