Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 1.0.2 under development

- New #91: Add method `ArrayHelper::group()` that groups the array according to a specified key (sagittaracc)
- New #5: Add method `ArrayHelper::indexAndRemoveKey()` that indexes and/or groups the array according
to a specified key and remove this key (vjik)

## 1.0.1 February 10, 2021

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Overall the helper has the following method groups.
### Transformation

- index
- indexAndRemoveKey
- group
- filter
- map
Expand Down
123 changes: 118 additions & 5 deletions src/ArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@ public static function removeValue(array &$array, $value): array
* ]
* ```
*
* @see self::indexAndRemoveKey
*
* @param array $array The array that needs to be indexed or grouped.
* @param Closure|string|null $key The column name or anonymous function which result will be used
* to index the array.
Expand All @@ -672,17 +674,116 @@ public static function removeValue(array &$array, $value): array
* @return array The indexed and/or grouped array.
*/
public static function index(array $array, $key, $groups = []): array
{
return self::indexArray($array, $key, $groups, false);
}

/**
* Indexes and/or groups the array according to a specified key and remove this key.
* The input should be either multidimensional array.
*
* `$groups` is an array of keys, that will be used to group the input array into one or more sub-arrays based
* on keys specified.
*
* If a value of an element corresponding to the key is `null` in addition to `$groups` not specified then
* the element is discarded.
*
* For example:
*
* ```php
* $array = [
* ['id' => '123', 'data' => 'abc', 'device' => 'laptop'],
* ['id' => '345', 'data' => 'def', 'device' => 'tablet'],
* ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'],
* ];
* $result = ArrayHelper::indexAndRemoveKey($array, 'id');
* ```
*
* The result will be an associative array, where the key is the value of `id` attribute and this attribute
* will be removed:
*
* ```php
* [
* '123' => ['data' => 'abc', 'device' => 'laptop'],
* '345' => ['data' => 'hgi', 'device' => 'smartphone']
* // The second element of an original array is overwritten by the last element because of the same id
* ]
* ```
*
* The anonymous function can be used in the array of grouping keys as well:
*
* ```php
* $result = ArrayHelper::index($array, 'data', [function ($element) {
* return $element['id'];
* }, 'device']);
* ```
*
* The result will be a multidimensional array grouped by `id` on the first level, by the `device` on
* the second one, indexed by the `data` on the third level and attribute `data` will be removed:
*
* ```php
* [
* '123' => [
* 'laptop' => [
* 'abc' => ['id' => '123', 'device' => 'laptop']
* ]
* ],
* '345' => [
* 'tablet' => [
* 'def' => ['id' => '345', 'device' => 'tablet']
* ],
* 'smartphone' => [
* 'hgi' => ['id' => '345', 'device' => 'smartphone']
* ]
* ]
* ]
* ```
*
* @see self::index
*
* @param array $array The array that needs to be indexed or grouped.
* @param string $key The column name will be used to index the array.
* @param Closure[]|string|string[]|null $groups The array of keys, that will be used to group the input array
* by one or more keys. If value for the particular element is null and `$groups` is not defined, the array element
* will be discarded. Otherwise, if `$groups` is specified, array element will be added to the result array without
* any key.
*
* @psalm-param array<mixed, array> $array
*
* @return array The indexed and/or grouped array.
*/
public static function indexAndRemoveKey(array $array, string $key, $groups = []): array
{
return self::indexArray($array, $key, $groups, true);
}

/**
* @see self::index
* @see self::indexAndRemoveKey
*
* @param Closure|string|null $key
* @param Closure[]|string|string[]|null $groups
*/
private static function indexArray(array $array, $key, $groups, bool $removeKey): array
{
$result = [];
$groups = (array)$groups;

/** @var mixed $element */
foreach ($array as $element) {
if (!is_array($element) && !is_object($element)) {
throw new InvalidArgumentException(
'index() can not get value from ' . gettype($element) .
'. The $array should be either multidimensional array or an array of objects.'
);
if (!is_array($element)) {
if ($removeKey) {
throw new InvalidArgumentException(
'indexAndRemoveKey() can not get value from ' . self::getVariableType($element) .
'. The $array should be either multidimensional array.'
);
}
if (!is_object($element)) {
throw new InvalidArgumentException(
'index() can not get value from ' . gettype($element) .
'. The $array should be either multidimensional array or an array of objects.'
);
}
}

$lastArray = &$result;
Expand All @@ -705,6 +806,10 @@ public static function index(array $array, $key, $groups = []): array
/** @var mixed */
$value = static::getValue($element, $key);
if ($value !== null) {
if ($removeKey) {
/** @psalm-suppress PossiblyInvalidArrayAccess */
unset($element[$key]);
}
$lastArray[static::normalizeArrayKey($value)] = $element;
}
}
Expand Down Expand Up @@ -1288,4 +1393,12 @@ private static function normalizeArrayKey($key): string
{
return is_float($key) ? NumericHelper::normalize($key) : (string)$key;
}

/**
* @param mixed $variable
*/
private static function getVariableType($variable): string
{
return is_object($variable) ? get_class($variable) : gettype($variable);
}
}
128 changes: 128 additions & 0 deletions tests/ArrayHelper/IndexAndRemoveKeyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Arrays\Tests\ArrayHelper;

use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use stdClass;
use Yiisoft\Arrays\ArrayHelper;

final class IndexAndRemoveKeyTest extends TestCase
{
public function testSimple(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
['id' => '345', 'data' => 'ghi'],
];

$result = ArrayHelper::indexAndRemoveKey($array, 'id');

$this->assertSame(
[
'123' => ['data' => 'abc'],
'345' => ['data' => 'ghi'],
],
$result
);
}

public function testWithElementsWithoutKey(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
['data' => 'ghi'],
];

$expected = [
123 => ['data' => 'abc'],
345 => ['data' => 'def'],
];

$result = ArrayHelper::indexAndRemoveKey($array, 'id');

$this->assertSame($expected, $result);
}

public function testSimpleGroupBy(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
['id' => '345', 'data' => 'ghi'],
];

$expected = [
'123' => [
'abc' => ['id' => '123'],
],
'345' => [
'def' => ['id' => '345'],
'ghi' => ['id' => '345'],
],
];

$this->assertSame($expected, ArrayHelper::indexAndRemoveKey($array, 'data', ['id']));
$this->assertSame($expected, ArrayHelper::indexAndRemoveKey($array, 'data', 'id'));
}

public function testStringElement(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
'data',
];

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('indexAndRemoveKey() can not get value from string. The $array should be either multidimensional array.');
ArrayHelper::indexAndRemoveKey($array, 'id');
}

public function testObjectElement(): void
{
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
new stdClass(),
];

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('indexAndRemoveKey() can not get value from stdClass. The $array should be either multidimensional array.');
ArrayHelper::indexAndRemoveKey($array, 'id');
}

public function testGroupByWithKey(): void
{
$this->expectException(InvalidArgumentException::class);
ArrayHelper::indexAndRemoveKey(['id' => '1'], 'id', ['id']);
}

/**
* @see https://github.com/yiisoft/yii2/issues/11739
*/
public function te1stIndexFloat(): void
{
$array = [
['id' => 1e6, 'data' => 'a'],
['id' => 1e32, 'data' => 'b'],
['id' => 1e64, 'data' => 'c'],
['id' => 1465540807.522109, 'data' => 'd'],
];

$expected = [
'1000000' => ['data' => 'a'],
'1.0E+32' => ['data' => 'b'],
'1.0E+64' => ['data' => 'c'],
'1465540807.5221' => ['data' => 'd'],
];

$result = ArrayHelper::index($array, 'id');

$this->assertEquals($expected, $result);
}
}