Fix #20306: Add new yii\helpers\ArrayHelper::flatten() method

This commit is contained in:
Kairat Jenishev
2025-01-06 20:53:58 +06:00
committed by GitHub
parent 3fc7e71c67
commit f94017ce4c
6 changed files with 452 additions and 49 deletions

View File

@ -348,3 +348,140 @@ ArrayHelper::isIn('a', new(ArrayObject['a']));
ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c'])
``` ```
## Преобразование многомерных массивов <span id="flattening-arrays"></span>
Метод `ArrayHelper::flatten()` позволяет преобразовать многомерный массив в одномерный, объединяя ключи.
### Основное использование
Чтобы преобразовать вложенный массив, просто передайте массив в метод `flatten()`:
```php
$array = [
'a' => [
'b' => [
'c' => 1,
'd' => 2,
],
'e' => 3,
],
'f' => 4,
];
$flattenedArray = ArrayHelper::flatten($array);
// Результат:
// [
// 'a.b.c' => 1,
// 'a.b.d' => 2,
// 'a.e' => 3,
// 'f' => 4,
// ]
```
### Пользовательский разделитель
Вы можете указать пользовательский (т.е. отличный от значения по умолчанию: `.`) разделитель для объединения ключей:
```php
$array = [
'a' => [
'b' => [
'c' => 1,
'd' => 2,
],
'e' => 3,
],
'f' => 4,
];
$flattenedArray = ArrayHelper::flatten($array, '_');
// Результат:
// [
// 'a_b_c' => 1,
// 'a_b_d' => 2,
// 'a_e' => 3,
// 'f' => 4,
// ]
```
### Обработка специальных символов в ключах
Метод `flatten()` может обрабатывать ключи со специальными символами:
```php
$array = [
'a.b' => [
'c.d' => 1,
],
'e.f' => 2,
];
$flattenedArray = ArrayHelper::flatten($array);
// Результат:
// [
// 'a.b.c.d' => 1,
// 'e.f' => 2,
// ]
```
### Смешанные типы данных
Метод `flatten()` работает с массивами, содержащими различные типы данных:
```php
$array = [
'a' => [
'b' => 'string',
'c' => 123,
'd' => true,
'e' => null,
],
'f' => [1, 2, 3],
];
$flattenedArray = ArrayHelper::flatten($array);
// Результат:
// [
// 'a.b' => 'string',
// 'a.c' => 123,
// 'a.d' => true,
// 'a.e' => null,
// 'f.0' => 1,
// 'f.1' => 2,
// 'f.2' => 3,
// ]
```
### Краевые случаи
Метод `flatten()` обрабатывает различные краевые случаи, такие как пустые массивы и значения, не являющиеся массивами:
```php
// Пустой массив
$array = [];
$flattenedArray = ArrayHelper::flatten($array);
// Результат: []
// Значение, не являющееся массивом
$array = 'string';
$flattenedArray = ArrayHelper::flatten($array);
// Результат:
// yii\base\InvalidArgumentException: Argument $array must be an array or implement Traversable
```
### Коллизии ключей
Когда ключи совпадают, метод `flatten()` перезапишет предыдущее значение:
```php
$array = [
'a' => [
'b' => 1,
],
'a.b' => 2,
];
$flattenedArray = ArrayHelper::flatten($array);
// Результат: ['a.b' => 2]
```

View File

@ -483,3 +483,140 @@ ArrayHelper::isIn('a', new ArrayObject(['a']));
// true // true
ArrayHelper::isSubset(new ArrayObject(['a', 'c']), new ArrayObject(['a', 'b', 'c'])); ArrayHelper::isSubset(new ArrayObject(['a', 'c']), new ArrayObject(['a', 'b', 'c']));
``` ```
## Flattening Arrays <span id="flattening-arrays"></span>
The `ArrayHelper::flatten()` method allows you to convert a multi-dimensional array into a single-dimensional array by concatenating keys.
### Basic Usage
To flatten a nested array, simply pass the array to the `flatten()` method:
```php
$array = [
'a' => [
'b' => [
'c' => 1,
'd' => 2,
],
'e' => 3,
],
'f' => 4,
];
$flattenedArray = ArrayHelper::flatten($array);
// Result:
// [
// 'a.b.c' => 1,
// 'a.b.d' => 2,
// 'a.e' => 3,
// 'f' => 4,
// ]
```
### Custom Separator
You can specify a custom separator to use when concatenating keys:
```php
$array = [
'a' => [
'b' => [
'c' => 1,
'd' => 2,
],
'e' => 3,
],
'f' => 4,
];
$flattenedArray = ArrayHelper::flatten($array, '_');
// Result:
// [
// 'a_b_c' => 1,
// 'a_b_d' => 2,
// 'a_e' => 3,
// 'f' => 4,
// ]
```
### Handling Special Characters in Keys
The `flatten()` method can handle keys with special characters:
```php
$array = [
'a.b' => [
'c.d' => 1,
],
'e.f' => 2,
];
$flattenedArray = ArrayHelper::flatten($array);
// Result:
// [
// 'a.b.c.d' => 1,
// 'e.f' => 2,
// ]
```
### Mixed Data Types
The `flatten()` method works with arrays containing different data types:
```php
$array = [
'a' => [
'b' => 'string',
'c' => 123,
'd' => true,
'e' => null,
],
'f' => [1, 2, 3],
];
$flattenedArray = ArrayHelper::flatten($array);
// Result:
// [
// 'a.b' => 'string',
// 'a.c' => 123,
// 'a.d' => true,
// 'a.e' => null,
// 'f.0' => 1,
// 'f.1' => 2,
// 'f.2' => 3,
// ]
```
### Edge Cases
The `flatten()` method handles various edge cases, such as empty arrays and non-array values:
```php
// Empty array
$array = [];
$flattenedArray = ArrayHelper::flatten($array);
// Result: []
// Non-array value
$array = 'string';
$flattenedArray = ArrayHelper::flatten($array);
// Result:
// yii\base\InvalidArgumentException: Argument $array must be an array or implement Traversable
```
### Key Collisions
When keys collide, the `flatten()` method will overwrite the previous value:
```php
$array = [
'a' => [
'b' => 1,
],
'a.b' => 2,
];
$flattenedArray = ArrayHelper::flatten($array);
// Result: ['a.b' => 2]
```

View File

@ -25,6 +25,7 @@ Yii Framework 2 Change Log
- Enh #20295: Add an ability to have wildcards in `yii\log\Target::$maskVars` array (xcopy) - Enh #20295: Add an ability to have wildcards in `yii\log\Target::$maskVars` array (xcopy)
- Bug #20296: Fix broken enum test (briedis) - Bug #20296: Fix broken enum test (briedis)
- Bug #20300: Clear stat cache in `FileCache::setValue()` (rob006) - Bug #20300: Clear stat cache in `FileCache::setValue()` (rob006)
- Enh #20306: Add new `yii\helpers\ArrayHelper::flatten()` method (xcopy)
2.0.51 July 18, 2024 2.0.51 July 18, 2024
-------------------- --------------------

View File

@ -1043,4 +1043,61 @@ class BaseArrayHelper
return $array; return $array;
} }
/**
* Flattens a multidimensional array into a one-dimensional array.
*
* This method recursively traverses the input array and concatenates the keys
* in a dot format to form a new key in the resulting array.
*
* Example:
*
* ```php
* $array = [
* 'A' => [1, 2],
* 'B' => [
* 'C' => 1,
* 'D' => 2,
* ],
* 'E' => 1,
* ];
* $result = \yii\helpers\ArrayHelper::flatten($array);
* // $result will be:
* // [
* // 'A.0' => 1
* // 'A.1' => 2
* // 'B.C' => 1
* // 'B.D' => 2
* // 'E' => 1
* // ]
* ```
*
* @param array $array the input array to be flattened in terms of name-value pairs.
* @param string $separator the separator to use between keys. Defaults to '.'.
*
* @return array the flattened array.
* @throws InvalidArgumentException if `$array` is neither traversable nor an array.
*/
public static function flatten($array, $separator = '.'): array
{
if (!static::isTraversable($array)) {
throw new InvalidArgumentException('Argument $array must be an array or implement Traversable');
}
$result = [];
foreach ($array as $key => $value) {
$newKey = $key;
if (is_array($value)) {
$flattenedArray = self::flatten($value, $separator);
foreach ($flattenedArray as $subKey => $subValue) {
$result[$newKey . $separator . $subKey] = $subValue;
}
} else {
$result[$newKey] = $value;
}
}
return $result;
}
} }

View File

@ -167,54 +167,6 @@ abstract class Target extends Component
} }
} }
/**
* Flattens a multidimensional array into a one-dimensional array.
*
* This method recursively traverses the input array and concatenates the keys
* to form a new key in the resulting array.
*
* Example:
*
* ```php
* $array = [
* 'A' => [1, 2],
* 'B' => [
* 'C' => 1,
* 'D' => 2,
* ],
* 'E' => 1,
* ];
* $result = \yii\log\Target::flatten($array);
* // result will be:
* // [
* // 'A.0' => 1
* // 'A.1' => 2
* // 'B.C' => 1
* // 'B.D' => 2
* // 'E' => 1
* // ]
* ```
*
* @param array $array the input array to be flattened.
* @param string $prefix the prefix to be added to each key in the resulting array.
*
* @return array the flattened array.
*/
private static function flatten($array, $prefix = ''): array
{
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, self::flatten($value, $prefix . $key . '.'));
} else {
$result[$prefix . $key] = $value;
}
}
return $result;
}
/** /**
* Generates the context information to be logged. * Generates the context information to be logged.
* The default implementation will dump user information, system variables, etc. * The default implementation will dump user information, system variables, etc.
@ -223,7 +175,7 @@ abstract class Target extends Component
protected function getContextMessage() protected function getContextMessage()
{ {
$context = ArrayHelper::filter($GLOBALS, $this->logVars); $context = ArrayHelper::filter($GLOBALS, $this->logVars);
$items = self::flatten($context); $items = ArrayHelper::flatten($context);
foreach ($this->maskVars as $var) { foreach ($this->maskVars as $var) {
foreach ($items as $key => $value) { foreach ($items as $key => $value) {
if (StringHelper::matchWildcard($var, $key, ['caseSensitive' => false])) { if (StringHelper::matchWildcard($var, $key, ['caseSensitive' => false])) {

View File

@ -1610,6 +1610,125 @@ class ArrayHelperTest extends TestCase
], ],
]; ];
} }
public function testFlatten()
{
// Test with deeply nested arrays
$array = [
'a' => [
'b' => [
'c' => [
'd' => 1,
'e' => 2,
],
'f' => 3,
],
'g' => 4,
],
'h' => 5,
];
$expected = [
'a.b.c.d' => 1,
'a.b.c.e' => 2,
'a.b.f' => 3,
'a.g' => 4,
'h' => 5,
];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Test with arrays containing different data types
$array = [
'a' => [
'b' => [
'c' => 'string',
'd' => 123,
'e' => true,
'f' => null,
],
'g' => [1, 2, 3],
],
];
$expected = [
'a.b.c' => 'string',
'a.b.d' => 123,
'a.b.e' => true,
'a.b.f' => null,
'a.g.0' => 1,
'a.g.1' => 2,
'a.g.2' => 3,
];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Test with arrays containing special characters in keys
$array = [
'a.b' => [
'c.d' => [
'e.f' => 1,
],
],
'g.h' => 2,
];
$expected = [
'a.b.c.d.e.f' => 1,
'g.h' => 2,
];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Test with custom separator
$array = [
'a' => [
'b' => [
'c' => [
'd' => 1,
'e' => 2,
],
'f' => 3,
],
'g' => 4,
],
'h' => 5,
];
$result = ArrayHelper::flatten($array, '_');
$expected = [
'a_b_c_d' => 1,
'a_b_c_e' => 2,
'a_b_f' => 3,
'a_g' => 4,
'h' => 5,
];
$this->assertEquals($expected, $result);
}
public function testFlattenEdgeCases()
{
// Empty array
$array = [];
$expected = [];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Non-array value
$array = 'string';
$expected = ['string'];
$this->expectException('yii\base\InvalidArgumentException');
$this->expectExceptionMessage('Argument $array must be an array or implement Traversable');
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Special characters in keys
$array = ['a.b' => ['c.d' => 1]];
$expected = ['a.b.c.d' => 1];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Mixed data types
$array = ['a' => ['b' => 'string', 'c' => 123, 'd' => true, 'e' => null]];
$expected = ['a.b' => 'string', 'a.c' => 123, 'a.d' => true, 'a.e' => null];
$this->assertEquals($expected, ArrayHelper::flatten($array));
// Key collisions
$array = ['a' => ['b' => 1], 'a.b' => 2];
$expected = ['a.b' => 2];
$this->assertEquals($expected, ArrayHelper::flatten($array));
}
} }
class Post1 class Post1