mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-10-31 10:39:59 +08:00 
			
		
		
		
	Fix #20306: Add new yii\helpers\ArrayHelper::flatten() method
				
					
				
			This commit is contained in:
		| @ -348,3 +348,140 @@ ArrayHelper::isIn('a', new(ArrayObject['a'])); | ||||
| 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] | ||||
| ``` | ||||
|  | ||||
| @ -483,3 +483,140 @@ ArrayHelper::isIn('a', new ArrayObject(['a'])); | ||||
| // true  | ||||
| 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] | ||||
| ``` | ||||
|  | ||||
| @ -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 | ||||
| -------------------- | ||||
|  | ||||
| @ -1043,4 +1043,61 @@ class BaseArrayHelper | ||||
|  | ||||
|         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; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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. | ||||
|      * The default implementation will dump user information, system variables, etc. | ||||
| @ -223,7 +175,7 @@ abstract class Target extends Component | ||||
|     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])) { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Kairat Jenishev
					Kairat Jenishev