diff --git a/docs/guide-ru/helper-array.md b/docs/guide-ru/helper-array.md index 2b4be9731a..f68de6c63f 100644 --- a/docs/guide-ru/helper-array.md +++ b/docs/guide-ru/helper-array.md @@ -47,7 +47,58 @@ $fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) { $username = ArrayHelper::getValue($comment, 'user.username', 'Unknown'); ``` -В случае если вы хотите получить значение и тут же удалить его из массива, вы можете использовать метод `remove` + +## Запись значений + +```php +$array = [ + 'key' => [ + 'in' => ['k' => 'value'] + ] +]; + +ArrayHelper::setValue($array, 'key.in', ['arr' => 'val']); +// путь для записи значения в `$array` можно указать как массив +ArrayHelper::setValue($array, ['key', 'in'], ['arr' => 'val']); +``` + +В результате исходное значение `$array['key']['in']` будет перезаписано новым + +```php +[ + 'key' => [ + 'in' => ['arr' => 'val'] + ] +] +``` + +Если путь содержит несуществующий ключ, то он будет создан + +```php +// Если `$array['key']['in']['arr0']` не пустой, то значение будет добавлено в массив +ArrayHelper::setValue($array, 'key.in.arr0.arr1', 'val'); + +// если необходимо полностью переопределить значение `$array['key']['in']['arr0']` +ArrayHelper::setValue($array, 'key.in.arr0', ['arr1' => 'val']); +``` + +Результатом будет следующим: + +```php +[ + 'key' => [ + 'in' => [ + 'k' => 'value', + 'arr0' => ['arr1' => 'val'] + ] + ] +] +``` + + +## Изъять значение из массива + +Если вы хотите получить значение и тут же удалить его из массива, вы можете использовать метод `remove` ```php $array = ['type' => 'A', 'options' => [1, 2]]; diff --git a/docs/guide/helper-array.md b/docs/guide/helper-array.md index 1b0d9a30bc..f317eab4de 100644 --- a/docs/guide/helper-array.md +++ b/docs/guide/helper-array.md @@ -53,7 +53,58 @@ Third optional argument is default value which is `null` if not specified. Could $username = ArrayHelper::getValue($comment, 'user.username', 'Unknown'); ``` -In case you want to get the value and then immediately remove it from array you can use `remove` method: + +## Setting values + +```php +$array = [ + 'key' => [ + 'in' => ['k' => 'value'] + ] +]; + +ArrayHelper::setValue($array, 'key.in', ['arr' => 'val']); +// the path to write the value in `$array` can be specified as an array +ArrayHelper::setValue($array, ['key', 'in'], ['arr' => 'val']); +``` + +As a result, initial value of `$array['key']['in']` will be overwritten by new value + +```php +[ + 'key' => [ + 'in' => ['arr' => 'val'] + ] +] +``` + +If the path contains a nonexistent key, it will be created + +```php +// if `$array['key']['in']['arr0']` is not empty, the value will be added to the array +ArrayHelper::setValue($array, 'key.in.arr0.arr1', 'val'); + +// if you want to completely override the value `$array['key']['in']['arr0']` +ArrayHelper::setValue($array, 'key.in.arr0', ['arr1' => 'val']); +``` + +The result will be + +```php +[ + 'key' => [ + 'in' => [ + 'k' => 'value', + 'arr0' => ['arr1' => 'val'] + ] + ] +] +``` + + +## Take a value from an array + +In case you want to get a value and then immediately remove it from an array you can use `remove` method: ```php $array = ['type' => 'A', 'options' => [1, 2]]; diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 651ec0afed..83b0b3eb0c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -30,6 +30,7 @@ Yii Framework 2 Change Log - Bug #14341: Fixed regression in error handling introduced by fixing #14264 (samdark) - Enh #13378: Added skipOnEmpty option to SluggableBehaviour (andrewnester) - Enh #14389: Optimize `Validator::validateAttributes()` by calling `attributeNames()` only once (nicdnep) +- Enh #6644: Added `yii\helpers\ArrayHelper::setValue()` (LAV45) - Enh #14105: Implemented a solution for retrieving DBMS constraints in `yii\db\Schema` (sergeymakinen) - Enh #14417: Added configuration for headers in PHP files generated by `message/extract` command (rob006) - Enh #13835: Added `yii\web\Request::getOrigin()` method that returns HTTP_ORIGIN of current CORS request (yyxx9988) diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index 0fcae0c7ad..27db4b1b13 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -214,6 +214,82 @@ class BaseArrayHelper return $default; } + /** + * Writes a value into an associative array at the key path specified. + * If there is no such key path yet, it will be created recursively. + * If the key exists, it will be overwritten. + * + * ```php + * $array = [ + * 'key' => [ + * 'in' => [ + * 'val1', + * 'key' => 'val' + * ] + * ] + * ]; + * ``` + * + * The result of `ArrayHelper::setValue($array, 'key.in.0', ['arr' => 'val']);` will be the following: + * + * ```php + * [ + * 'key' => [ + * 'in' => [ + * ['arr' => 'val'], + * 'key' => 'val' + * ] + * ] + * ] + * + * ``` + * + * The result of + * `ArrayHelper::setValue($array, 'key.in', ['arr' => 'val']);` or + * `ArrayHelper::setValue($array, ['key', 'in'], ['arr' => 'val']);` + * will be the following: + * + * ```php + * [ + * 'key' => [ + * 'in' => [ + * 'arr' => 'val' + * ] + * ] + * ] + * ``` + * + * @param array $array the array to write the value to + * @param string|array|null $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 a dot + * you can also describe the path as an array of keys + * if the path is null then `$array` will be assigned the `$value` + * @param mixed $value the value to be written + * @since 2.0.13 + */ + public static function setValue(&$array, $path, $value) + { + if ($path === null) { + $array = $value; + return; + } + + $keys = is_array($path) ? $path : explode('.', $path); + + while (count($keys) > 1) { + $key = array_shift($keys); + if (!isset($array[$key])) { + $array[$key] = []; + } + if (!is_array($array[$key])) { + $array[$key] = [$array[$key]]; + } + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + } + /** * Removes an item from an array and returns the value. If the key does not exist in the array, the default value * will be returned instead. diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index fb2cc57848..33b203c5cc 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -821,6 +821,246 @@ class ArrayHelperTest extends TestCase $this->assertEquals(23, ArrayHelper::getValue($arrayObject, 'nonExisting')); } + /** + * Data provider for [[testSetValue()]] + * @return array test data + */ + public function dataProviderSetValue() + { + return [ + [ + [ + 'key1' => 'val1', + 'key2' => 'val2', + ], + 'key', 'val', + [ + 'key1' => 'val1', + 'key2' => 'val2', + 'key' => 'val', + ], + ], + [ + [ + 'key1' => 'val1', + 'key2' => 'val2', + ], + 'key2', 'val', + [ + 'key1' => 'val1', + 'key2' => 'val', + ], + ], + + [ + [ + 'key1' => 'val1', + ], + 'key.in', 'val', + [ + 'key1' => 'val1', + 'key' => ['in' => 'val'], + ], + ], + [ + [ + 'key' => 'val1', + ], + 'key.in', 'val', + [ + 'key' => [ + 'val1', + 'in' => 'val' + ], + ], + ], + [ + [ + 'key' => 'val1', + ], + 'key', ['in' => 'val'], + [ + 'key' => ['in' => 'val'], + ], + ], + + [ + [ + 'key1' => 'val1', + ], + 'key.in.0', 'val', + [ + 'key1' => 'val1', + 'key' => [ + 'in' => ['val'] + ], + ], + ], + + [ + [ + 'key1' => 'val1', + ], + 'key.in.arr', 'val', + [ + 'key1' => 'val1', + 'key' => [ + 'in' => [ + 'arr' => 'val' + ] + ], + ], + ], + [ + [ + 'key1' => 'val1', + ], + 'key.in.arr', ['val'], + [ + 'key1' => 'val1', + 'key' => [ + 'in' => [ + 'arr' => ['val'] + ] + ], + ], + ], + [ + [ + 'key' => [ + 'in' => ['val1'] + ], + ], + 'key.in.arr', 'val', + [ + 'key' => [ + 'in' => [ + 'val1', + 'arr' => 'val' + ] + ], + ], + ], + + [ + [ + 'key' => ['in' => 'val1'], + ], + 'key.in.arr', ['val'], + [ + 'key' => [ + 'in' => [ + 'val1', + 'arr' => ['val'] + ] + ], + ], + ], + [ + [ + 'key' => [ + 'in' => [ + 'val1', + 'key' => 'val' + ] + ], + ], + 'key.in.0', ['arr' => 'val'], + [ + 'key' => [ + 'in' => [ + ['arr' => 'val'], + 'key' => 'val' + ] + ], + ], + ], + [ + [ + 'key' => [ + 'in' => [ + 'val1', + 'key' => 'val' + ] + ], + ], + 'key.in', ['arr' => 'val'], + [ + 'key' => [ + 'in' => ['arr' => 'val'] + ], + ], + ], + [ + [ + 'key' => [ + 'in' => [ + 'key' => 'val', + 'data' => [ + 'attr1', + 'attr2', + 'attr3', + ] + ] + ], + ], + 'key.in.schema', 'array', + [ + 'key' => [ + 'in' => [ + 'key' => 'val', + 'schema' => 'array', + 'data' => [ + 'attr1', + 'attr2', + 'attr3', + ] + ] + ], + ], + ], + [ + [ + 'key' => [ + 'in.array' => [ + 'key' => 'val', + ] + ], + ], + ['key', 'in.array', 'ok.schema'], 'array', + [ + 'key' => [ + 'in.array' => [ + 'key' => 'val', + 'ok.schema' => 'array', + ] + ], + ], + ], + [ + [ + 'key' => ['val'], + ], + null, 'data', + 'data', + ], + ]; + } + + /** + * @dataProvider dataProviderSetValue + * + * @param array $array_input + * @param string|array|null $key + * @param mixed $value + * @param mixed $expected + */ + public function testSetValue($array_input, $key, $value, $expected) + { + ArrayHelper::setValue($array_input, $key, $value); + $this->assertEquals($expected, $array_input); + } + public function testIsAssociative() { $this->assertFalse(ArrayHelper::isAssociative('test'));