diff --git a/docs/guide-ru/helper-array.md b/docs/guide-ru/helper-array.md
index 92dcdf558b8..94a60765c28 100644
--- a/docs/guide-ru/helper-array.md
+++ b/docs/guide-ru/helper-array.md
@@ -348,3 +348,140 @@ ArrayHelper::isIn('a', new(ArrayObject['a']));
ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c'])
```
+
+## Преобразование многомерных массивов
+
+Метод `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]
+```
diff --git a/docs/guide/helper-array.md b/docs/guide/helper-array.md
index 19c3c03ad5f..e7b1641fff8 100644
--- a/docs/guide/helper-array.md
+++ b/docs/guide/helper-array.md
@@ -483,3 +483,140 @@ ArrayHelper::isIn('a', new ArrayObject(['a']));
// true
ArrayHelper::isSubset(new ArrayObject(['a', 'c']), new ArrayObject(['a', 'b', 'c']));
```
+
+## Flattening Arrays
+
+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]
+```
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index 391edd84d92..67c0fc35e4f 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -25,6 +25,7 @@ Yii Framework 2 Change Log
- Enh #20295: Add an ability to have wildcards in `yii\log\Target::$maskVars` array (xcopy)
- Bug #20296: Fix broken enum test (briedis)
- 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
--------------------
diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php
index bc770f96cb7..c24cc99f970 100644
--- a/framework/helpers/BaseArrayHelper.php
+++ b/framework/helpers/BaseArrayHelper.php
@@ -1043,4 +1043,61 @@ public static function recursiveSort(array &$array, $sorter = null)
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;
+ }
}
diff --git a/framework/log/Target.php b/framework/log/Target.php
index a56d9efbf00..1d9a22f23d9 100644
--- a/framework/log/Target.php
+++ b/framework/log/Target.php
@@ -167,54 +167,6 @@ public function collect($messages, $final)
}
}
- /**
- * 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.
* The default implementation will dump user information, system variables, etc.
@@ -223,7 +175,7 @@ private static function flatten($array, $prefix = ''): array
protected function getContextMessage()
{
$context = ArrayHelper::filter($GLOBALS, $this->logVars);
- $items = self::flatten($context);
+ $items = ArrayHelper::flatten($context);
foreach ($this->maskVars as $var) {
foreach ($items as $key => $value) {
if (StringHelper::matchWildcard($var, $key, ['caseSensitive' => false])) {
diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php
index a086503006b..4e6b790bebc 100644
--- a/tests/framework/helpers/ArrayHelperTest.php
+++ b/tests/framework/helpers/ArrayHelperTest.php
@@ -1610,6 +1610,125 @@ public function dataProviderRecursiveSort()
],
];
}
+
+ 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